• 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

1.04
/projects/igniteui-angular/src/lib/tabs/tabs.directive.ts
1
import {
2
    AfterViewInit, ChangeDetectorRef, ContentChildren, Directive, EventEmitter,
3
    Inject,
4
    Input, OnDestroy, Output, QueryList, booleanAttribute
5
} from '@angular/core';
6
import { Subscription } from 'rxjs';
7
import { Direction, IgxCarouselComponentBase } from '../carousel/carousel-base';
8
import { IBaseEventArgs } from '../core/utils';
9
import { IgxAngularAnimationService } from '../services/animation/angular-animation-service';
10
import { AnimationService } from '../services/animation/animation';
11
import { IgxDirectionality } from '../services/direction/directionality';
12
import { IgxTabItemDirective } from './tab-item.directive';
13
import { IgxTabContentBase, IgxTabsBase } from './tabs.base';
14

15
export interface ITabsBaseEventArgs extends IBaseEventArgs {
16
    readonly owner: IgxTabsDirective;
17
}
18

19
export interface ITabsSelectedIndexChangingEventArgs extends ITabsBaseEventArgs {
20
    cancel: boolean;
21
    readonly oldIndex: number;
22
    newIndex: number;
23
}
24

25
export interface ITabsSelectedItemChangeEventArgs extends ITabsBaseEventArgs {
26
    readonly oldItem: IgxTabItemDirective;
27
    readonly newItem: IgxTabItemDirective;
28
}
29

30
@Directive()
31
export abstract class IgxTabsDirective extends IgxCarouselComponentBase implements IgxTabsBase, AfterViewInit, OnDestroy {
2✔
32

33
    /**
34
     * Gets/Sets the index of the selected item.
35
     * Default value is 0 if contents are defined otherwise defaults to -1.
36
     */
37
    @Input()
38
    public get selectedIndex(): number {
UNCOV
39
        return this._selectedIndex;
×
40
    }
41

42
    public set selectedIndex(value: number) {
UNCOV
43
        if (this._selectedIndex !== value) {
×
UNCOV
44
            let newIndex = value;
×
UNCOV
45
            const oldIndex = this._selectedIndex;
×
UNCOV
46
            const args: ITabsSelectedIndexChangingEventArgs = {
×
47
                owner: this,
48
                cancel: false,
49
                oldIndex,
50
                newIndex
51
            };
UNCOV
52
            this.selectedIndexChanging.emit(args);
×
53

UNCOV
54
            if (!args.cancel) {
×
UNCOV
55
                newIndex = args.newIndex;
×
UNCOV
56
                this._selectedIndex = newIndex;
×
UNCOV
57
                this.selectedIndexChange.emit(this._selectedIndex);
×
58
            }
59

UNCOV
60
            this.updateSelectedTabs(oldIndex);
×
61
        }
62
    }
63

64
    /**
65
     * Enables/disables the transition animation of the contents.
66
     */
67
    @Input({ transform: booleanAttribute })
UNCOV
68
    public disableAnimation = false;
×
69

70
    /**
71
     * Output to enable support for two-way binding on [(selectedIndex)]
72
     */
73
    @Output()
UNCOV
74
    public selectedIndexChange = new EventEmitter<number>();
×
75

76
    /**
77
     * Emitted when the selected index is about to change.
78
     */
79
    @Output()
UNCOV
80
    public selectedIndexChanging = new EventEmitter<ITabsSelectedIndexChangingEventArgs>();
×
81

82
    /**
83
     * Emitted when the selected item is changed.
84
     */
85
    @Output()
UNCOV
86
    public selectedItemChange = new EventEmitter<ITabsSelectedItemChangeEventArgs>();
×
87

88
    /**
89
     * Returns the items.
90
     */
91
    @ContentChildren(IgxTabItemDirective)
92
    public items: QueryList<IgxTabItemDirective>;
93

94
    /**
95
     * Gets the selected item.
96
     */
97
    public get selectedItem(): IgxTabItemDirective {
UNCOV
98
        return this.items && this.selectedIndex >= 0 && this.selectedIndex < this.items.length ?
×
99
            this.items.get(this.selectedIndex) : null;
100
    }
101

102
    /** @hidden */
103
    @ContentChildren(IgxTabContentBase, { descendants: true })
104
    public panels: QueryList<IgxTabContentBase>;
105

106
    /** @hidden */
107
    protected override currentItem: IgxTabItemDirective;
108
    /** @hidden */
109
    protected override previousItem: IgxTabItemDirective;
110
    /** @hidden */
111
    protected componentName: string;
112

UNCOV
113
    private _selectedIndex = -1;
×
114
    private _itemChanges$: Subscription;
115

116
    /** @hidden */
117
    constructor(
118
        @Inject(IgxAngularAnimationService) animationService: AnimationService,
119
        cdr: ChangeDetectorRef,
UNCOV
120
        public dir: IgxDirectionality) {
×
UNCOV
121
        super(animationService, cdr);
×
122
    }
123

124
    /** @hidden */
125
    public ngAfterViewInit(): void {
UNCOV
126
        if (this._selectedIndex === -1) {
×
UNCOV
127
            const hasSelectedTab = this.items.some((tab, i) => {
×
UNCOV
128
                if (tab.selected) {
×
UNCOV
129
                    this._selectedIndex = i;
×
130
                }
UNCOV
131
                return tab.selected;
×
132
            });
133

UNCOV
134
            if (!hasSelectedTab && this.hasPanels) {
×
UNCOV
135
                this._selectedIndex = 0;
×
136
            }
137
        }
138

139
        // Use promise to avoid expression changed after check error
UNCOV
140
        Promise.resolve().then(() => {
×
UNCOV
141
            this.updateSelectedTabs(null, false);
×
142
        });
143

UNCOV
144
        this._itemChanges$ = this.items.changes.subscribe(() => {
×
UNCOV
145
            this.onItemChanges();
×
146
        });
147

UNCOV
148
        this.setAttributes();
×
149
    }
150

151
    /** @hidden */
152
    public ngOnDestroy(): void {
UNCOV
153
        if (this._itemChanges$) {
×
UNCOV
154
            this._itemChanges$.unsubscribe();
×
155
        }
156
    }
157

158
    /** @hidden */
159
    public selectTab(tab: IgxTabItemDirective, selected: boolean): void {
UNCOV
160
        if (!this.items) {
×
UNCOV
161
            return;
×
162
        }
163

UNCOV
164
        const tabs = this.items.toArray();
×
165

UNCOV
166
        if (selected) {
×
UNCOV
167
            const index = tabs.indexOf(tab);
×
UNCOV
168
            if (index > -1) {
×
UNCOV
169
                this.selectedIndex = index;
×
170
            }
171
        } else {
UNCOV
172
            if (tabs.every(t => !t.selected)) {
×
UNCOV
173
                this.selectedIndex = -1;
×
174
            }
175
        }
176
    }
177

178
    /** @hidden */
179
    protected getPreviousElement(): HTMLElement {
UNCOV
180
        return this.previousItem.panelComponent.nativeElement;
×
181
    }
182

183
    /** @hidden */
184
    protected getCurrentElement(): HTMLElement {
UNCOV
185
        return this.currentItem.panelComponent.nativeElement;
×
186
    }
187

188
    /** @hidden */
189
    protected scrollTabHeaderIntoView() {
190
    }
191

192
    /** @hidden */
193
    protected onItemChanges() {
UNCOV
194
        this.setAttributes();
×
195

196
        // Check if there is selected tab
UNCOV
197
        let selectedIndex = -1;
×
UNCOV
198
        this.items.some((tab, i) => {
×
UNCOV
199
            if (tab.selected) {
×
UNCOV
200
                selectedIndex = i;
×
201
            }
UNCOV
202
            return tab.selected;
×
203
        });
204

UNCOV
205
        if (selectedIndex >= 0) {
×
206
            // Set the selected index to the tab that has selected=true
UNCOV
207
            Promise.resolve().then(() => {
×
UNCOV
208
                this.selectedIndex = selectedIndex;
×
209
            });
210
        } else {
UNCOV
211
            if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
×
212
                // Select the tab on the same index the previous selected tab was
UNCOV
213
                Promise.resolve().then(() => {
×
UNCOV
214
                    this.updateSelectedTabs(null);
×
215
                });
UNCOV
216
            } else if (this.selectedIndex >= this.items.length) {
×
217
                // Select the last tab
UNCOV
218
                Promise.resolve().then(() => {
×
UNCOV
219
                    this.selectedIndex = this.items.length - 1;
×
220
                });
221
            }
222
        }
223
    }
224

225
    private setAttributes() {
UNCOV
226
        this.items.forEach(item => {
×
UNCOV
227
            if (item.panelComponent && !item.headerComponent.nativeElement.getAttribute('id')) {
×
UNCOV
228
                const id = this.getNextTabId();
×
UNCOV
229
                const tabHeaderId = `${this.componentName}-header-${id}`;
×
UNCOV
230
                const tabPanelId = `${this.componentName}-content-${id}`;
×
231

UNCOV
232
                this.setHeaderAttribute(item, 'id', tabHeaderId);
×
UNCOV
233
                this.setHeaderAttribute(item, 'aria-controls', tabPanelId);
×
UNCOV
234
                this.setPanelAttribute(item, 'id', tabPanelId);
×
UNCOV
235
                this.setPanelAttribute(item, 'aria-labelledby', tabHeaderId);
×
236
            }
237
        });
238
    }
239

240
    private setHeaderAttribute(item: IgxTabItemDirective, attrName: string, value: string) {
UNCOV
241
        item.headerComponent.nativeElement.setAttribute(attrName, value);
×
242
    }
243

244
    private setPanelAttribute(item: IgxTabItemDirective, attrName: string, value: string) {
UNCOV
245
        item.panelComponent.nativeElement.setAttribute(attrName, value);
×
246
    }
247

248
    private get hasPanels() {
UNCOV
249
        return this.panels && this.panels.length;
×
250
    }
251

252
    private updateSelectedTabs(oldSelectedIndex: number, raiseEvent = true) {
×
UNCOV
253
        if (!this.items) {
×
UNCOV
254
            return;
×
255
        }
256

257
        let newTab: IgxTabItemDirective;
UNCOV
258
        const oldTab = this.currentItem;
×
259

260
        // First select the new tab
UNCOV
261
        if (this._selectedIndex >= 0 && this._selectedIndex < this.items.length) {
×
UNCOV
262
            newTab = this.items.get(this._selectedIndex);
×
UNCOV
263
            newTab.selected = true;
×
264
        }
265
        // Then unselect the other tabs
UNCOV
266
        this.items.forEach((tab, i) => {
×
UNCOV
267
            if (i !== this._selectedIndex) {
×
UNCOV
268
                tab.selected = false;
×
269
            }
270
        });
271

UNCOV
272
        if (this._selectedIndex !== oldSelectedIndex) {
×
UNCOV
273
            this.scrollTabHeaderIntoView();
×
UNCOV
274
            this.triggerPanelAnimations(oldSelectedIndex);
×
275

UNCOV
276
            if (raiseEvent && newTab !== oldTab) {
×
UNCOV
277
                this.selectedItemChange.emit({
×
278
                    owner: this,
279
                    newItem: newTab,
280
                    oldItem: oldTab
281
                });
282
            }
283
        }
284
    }
285

286
    private triggerPanelAnimations(oldSelectedIndex: number) {
UNCOV
287
        const item = this.items.get(this._selectedIndex);
×
288

UNCOV
289
        if (item &&
×
290
            !this.disableAnimation &&
291
            this.hasPanels &&
292
            this.currentItem &&
293
            !this.currentItem.selected) {
UNCOV
294
            item.direction = (!this.dir.rtl && this._selectedIndex > oldSelectedIndex) ||
×
295
                (this.dir.rtl && this._selectedIndex < oldSelectedIndex)
296
                ? Direction.NEXT : Direction.PREV;
297

UNCOV
298
            if (this.previousItem && this.previousItem.previous) {
×
299
                this.previousItem.previous = false;
×
300
            }
UNCOV
301
            this.currentItem.direction = item.direction;
×
302

UNCOV
303
            this.previousItem = this.currentItem;
×
UNCOV
304
            this.currentItem = item;
×
UNCOV
305
            this.triggerAnimations();
×
306
        } else {
UNCOV
307
            this.currentItem = item;
×
308
        }
309
    }
310

311
    /** @hidden */
312
    protected abstract getNextTabId();
313
}
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