• 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.69
/projects/igniteui-angular/src/lib/accordion/accordion.component.ts
1
import {
2
    AfterContentInit, AfterViewInit, ChangeDetectorRef, Component, ContentChildren, EventEmitter,
3
    HostBinding, Input, OnDestroy, Output, QueryList, booleanAttribute
4
} from '@angular/core';
5
import { fromEvent, Subject } from 'rxjs';
6
import { takeUntil } from 'rxjs/operators';
7
import { ACCORDION_NAVIGATION_KEYS } from '../core/utils';
8
import {
9
    IExpansionPanelCancelableEventArgs,
10
    IExpansionPanelEventArgs, IgxExpansionPanelBase
11
} from '../expansion-panel/expansion-panel.common';
12
import { IgxExpansionPanelComponent } from '../expansion-panel/expansion-panel.component';
13
import { ToggleAnimationSettings } from '../expansion-panel/toggle-animation-component';
14

15
export interface IAccordionEventArgs extends IExpansionPanelEventArgs {
16
    owner: IgxAccordionComponent;
17
    /** Provides a reference to the `IgxExpansionPanelComponent` which was expanded/collapsed. */
18
    panel: IgxExpansionPanelBase;
19
}
20

21
export interface IAccordionCancelableEventArgs extends IExpansionPanelCancelableEventArgs {
22
    owner: IgxAccordionComponent;
23
    /** Provides a reference to the `IgxExpansionPanelComponent` which is currently expanding/collapsing. */
24
    panel: IgxExpansionPanelBase;
25
}
26

27
let NEXT_ID = 0;
2✔
28

29
/**
30
 * IgxAccordion is a container-based component that contains that can house multiple expansion panels.
31
 *
32
 * @igxModule IgxAccordionModule
33
 *
34
 * @igxKeywords accordion
35
 *
36
 * @igxGroup Layouts
37
 *
38
 * @remarks
39
 * The Ignite UI for Angular Accordion component enables the user to navigate among multiple collapsing panels
40
 * displayed in a single container.
41
 * The accordion offers keyboard navigation and API to control the underlying panels' expansion state.
42
 *
43
 * @example
44
 * ```html
45
 * <igx-accordion>
46
 *   <igx-expansion-panel *ngFor="let panel of panels">
47
 *       ...
48
 *   </igx-expansion-panel>
49
 * </igx-accordion>
50
 * ```
51
 */
52
@Component({
53
    selector: 'igx-accordion',
54
    templateUrl: 'accordion.component.html',
55
    standalone: true
56
})
57
export class IgxAccordionComponent implements AfterContentInit, AfterViewInit, OnDestroy {
2✔
58
    /**
59
     * Get/Set the `id` of the accordion component.
60
     * Default value is `"igx-accordion-0"`;
61
     * ```html
62
     * <igx-accordion id="my-first-accordion"></igx-accordion>
63
     * ```
64
     * ```typescript
65
     * const accordionId = this.accordion.id;
66
     * ```
67
     */
68
    @HostBinding('attr.id')
69
    @Input()
UNCOV
70
    public id = `igx-accordion-${NEXT_ID++}`;
×
71

72
    /** @hidden @internal **/
73
    @HostBinding('class.igx-accordion')
UNCOV
74
    public cssClass = 'igx-accordion';
×
75

76
    /** @hidden @internal **/
77
    @HostBinding('style.display')
UNCOV
78
    public displayStyle = 'block';
×
79

80
    /**
81
     * Get/Set the animation settings that panels should use when expanding/collpasing.
82
     *
83
     * ```html
84
     * <igx-accordion [animationSettings]="customAnimationSettings"></igx-accordion>
85
     * ```
86
     *
87
     * ```typescript
88
     * const customAnimationSettings: ToggleAnimationSettings = {
89
     *      openAnimation: growVerIn,
90
     *      closeAnimation: growVerOut
91
     * };
92
     *
93
     * this.accordion.animationSettings = customAnimationSettings;
94
     * ```
95
     */
96
    @Input()
97
    public get animationSettings(): ToggleAnimationSettings {
UNCOV
98
        return this._animationSettings;
×
99
    }
100

101
    public set animationSettings(value: ToggleAnimationSettings) {
UNCOV
102
        this._animationSettings = value;
×
UNCOV
103
        this.updatePanelsAnimation();
×
104
    }
105

106
    /**
107
     * Get/Set how the accordion handles the expansion of the projected expansion panels.
108
     * If set to `true`, only a single panel can be expanded at a time, collapsing all others
109
     *
110
     * ```html
111
     * <igx-accordion [singleBranchExpand]="true">
112
     * ...
113
     * </igx-accordion>
114
     * ```
115
     *
116
     * ```typescript
117
     * this.accordion.singleBranchExpand = false;
118
     * ```
119
     */
120
    @Input({ transform: booleanAttribute })
121
    public get singleBranchExpand(): boolean {
UNCOV
122
        return this._singleBranchExpand;
×
123
    }
124

125
    public set singleBranchExpand(val: boolean) {
UNCOV
126
        this._singleBranchExpand = val;
×
UNCOV
127
        if (val) {
×
UNCOV
128
            this.collapseAllExceptLast();
×
129
        }
130
    }
131

132
    /**
133
     * Emitted before a panel is expanded.
134
     *
135
     * @remarks
136
     * This event is cancelable.
137
     *
138
     * ```html
139
     * <igx-accordion (panelExpanding)="handlePanelExpanding($event)">
140
     * </igx-accordion>
141
     * ```
142
     *
143
     *```typescript
144
     * public handlePanelExpanding(event: IExpansionPanelCancelableEventArgs){
145
     *  const expandedPanel: IgxExpansionPanelComponent = event.panel;
146
     *  if (expandedPanel.disabled) {
147
     *      event.cancel = true;
148
     *  }
149
     * }
150
     *```
151
     */
152
    @Output()
UNCOV
153
    public panelExpanding = new EventEmitter<IAccordionCancelableEventArgs>();
×
154

155
    /**
156
     * Emitted after a panel has been expanded.
157
     *
158
     * ```html
159
     * <igx-accordion (panelExpanded)="handlePanelExpanded($event)">
160
     * </igx-accordion>
161
     * ```
162
     *
163
     *```typescript
164
     * public handlePanelExpanded(event: IExpansionPanelCancelableEventArgs) {
165
     *  const expandedPanel: IgxExpansionPanelComponent = event.panel;
166
     *  console.log("Panel is expanded: ", expandedPanel.id);
167
     * }
168
     *```
169
     */
170
    @Output()
UNCOV
171
    public panelExpanded = new EventEmitter<IAccordionEventArgs>();
×
172

173
    /**
174
     * Emitted before a panel is collapsed.
175
     *
176
     * @remarks
177
     * This event is cancelable.
178
     *
179
     * ```html
180
     * <igx-accordion (panelCollapsing)="handlePanelCollapsing($event)">
181
     * </igx-accordion>
182
     * ```
183
     */
184
    @Output()
UNCOV
185
    public panelCollapsing = new EventEmitter<IAccordionCancelableEventArgs>();
×
186

187
    /**
188
     * Emitted after a panel has been collapsed.
189
     *
190
     * ```html
191
     * <igx-accordion (panelCollapsed)="handlePanelCollapsed($event)">
192
     * </igx-accordion>
193
     * ```
194
     */
195
    @Output()
UNCOV
196
    public panelCollapsed = new EventEmitter<IAccordionEventArgs>();
×
197

198
    /**
199
     * Get all panels.
200
     *
201
     * ```typescript
202
     * const panels: IgxExpansionPanelComponent[] = this.accordion.panels;
203
     * ```
204
     */
205
    public get panels(): IgxExpansionPanelComponent[] {
UNCOV
206
        return this._panels?.toArray();
×
207
    }
208

209
    @ContentChildren(IgxExpansionPanelComponent)
210
    private _panels!: QueryList<IgxExpansionPanelComponent>;
211
    private _animationSettings!: ToggleAnimationSettings;
212
    private _expandedPanels!: Set<IgxExpansionPanelComponent>;
213
    private _expandingPanels!: Set<IgxExpansionPanelComponent>;
UNCOV
214
    private _destroy$ = new Subject<void>();
×
UNCOV
215
    private _unsubChildren$ = new Subject<void>();
×
216
    private _enabledPanels!: IgxExpansionPanelComponent[];
UNCOV
217
    private _singleBranchExpand = false;
×
218

UNCOV
219
    constructor(private cdr: ChangeDetectorRef) { }
×
220

221
    /** @hidden @internal **/
222
    public ngAfterContentInit(): void {
UNCOV
223
        this.updatePanelsAnimation();
×
UNCOV
224
        if (this.singleBranchExpand) {
×
225
            this.collapseAllExceptLast();
×
226
        }
227
    }
228

229
    /** @hidden @internal **/
230
    public ngAfterViewInit(): void {
UNCOV
231
        this._expandedPanels = new Set<IgxExpansionPanelComponent>(this._panels.filter(panel => !panel.collapsed));
×
UNCOV
232
        this._expandingPanels = new Set<IgxExpansionPanelComponent>();
×
UNCOV
233
        this._panels.changes.pipe(takeUntil(this._destroy$)).subscribe(() => {
×
234
            this.subToChanges();
×
235
        });
UNCOV
236
        this.subToChanges();
×
237
    }
238

239
    /** @hidden @internal */
240
    public ngOnDestroy(): void {
UNCOV
241
        this._unsubChildren$.next();
×
UNCOV
242
        this._unsubChildren$.complete();
×
UNCOV
243
        this._destroy$.next();
×
UNCOV
244
        this._destroy$.complete();
×
245
    }
246

247
    /**
248
     * Expands all collapsed expansion panels.
249
     *
250
     * ```typescript
251
     * accordion.expandAll();
252
     * ```
253
     */
254
    public expandAll(): void {
UNCOV
255
        if (this.singleBranchExpand) {
×
UNCOV
256
            for (let i = 0; i < this.panels.length - 1; i++) {
×
UNCOV
257
                this.panels[i].collapse();
×
258
            }
UNCOV
259
            this._panels.last.expand();
×
UNCOV
260
            return;
×
261
        }
262

UNCOV
263
        this.panels.forEach(panel => panel.expand());
×
264
    }
265

266
    /**
267
     * Collapses all expanded expansion panels.
268
     *
269
     * ```typescript
270
     * accordion.collapseAll();
271
     * ```
272
     */
273
    public collapseAll(): void {
UNCOV
274
        this.panels.forEach(panel => panel.collapse());
×
275
    }
276

277
    private collapseAllExceptLast(): void {
UNCOV
278
        const lastExpanded = this.panels?.filter(p => !p.collapsed && !p.header.disabled).pop();
×
UNCOV
279
        this.panels?.forEach((p: IgxExpansionPanelComponent) => {
×
UNCOV
280
            if (p !== lastExpanded && !p.header.disabled) {
×
UNCOV
281
                p.collapsed = true;
×
282
            }
283
        });
UNCOV
284
        this.cdr.markForCheck();
×
285
    }
286

287
    private handleKeydown(event: KeyboardEvent, panel: IgxExpansionPanelComponent): void {
UNCOV
288
        const key = event.key.toLowerCase();
×
UNCOV
289
        if (!(ACCORDION_NAVIGATION_KEYS.has(key))) {
×
290
            return;
×
291
        }
292
        // TO DO: if we ever want to improve the performance of the accordion,
293
        // enabledPanels could be cached (by making a disabledChange emitter on the panel header)
UNCOV
294
        this._enabledPanels = this._panels.filter(p => !p.header.disabled);
×
UNCOV
295
        event.preventDefault();
×
UNCOV
296
        this.handleNavigation(event, panel);
×
297
    }
298

299
    private handleNavigation(event: KeyboardEvent, panel: IgxExpansionPanelComponent): void {
UNCOV
300
        switch (event.key.toLowerCase()) {
×
301
            case 'home':
UNCOV
302
                this._enabledPanels[0].header.innerElement.focus();
×
UNCOV
303
                break;
×
304
            case 'end':
UNCOV
305
                this._enabledPanels[this._enabledPanels.length - 1].header.innerElement.focus();
×
UNCOV
306
                break;
×
307
            case 'arrowup':
308
            case 'up':
UNCOV
309
                this.handleUpDownArrow(true, event, panel);
×
UNCOV
310
                break;
×
311
            case 'arrowdown':
312
            case 'down':
UNCOV
313
                this.handleUpDownArrow(false, event, panel);
×
UNCOV
314
                break;
×
315
        }
316
    }
317

318
    private handleUpDownArrow(isUp: boolean, event: KeyboardEvent, panel: IgxExpansionPanelComponent): void {
UNCOV
319
        if (!event.altKey) {
×
UNCOV
320
            const focusedPanel = panel;
×
UNCOV
321
            const next = this.getNextPanel(focusedPanel, isUp ? -1 : 1);
×
UNCOV
322
            if (next === focusedPanel) {
×
323
                return;
×
324
            }
UNCOV
325
            next.header.innerElement.focus();
×
326
        }
UNCOV
327
        if (event.altKey && event.shiftKey) {
×
UNCOV
328
            if (isUp) {
×
UNCOV
329
                this._enabledPanels.forEach(p => p.collapse());
×
330
            } else {
UNCOV
331
                if (this.singleBranchExpand) {
×
UNCOV
332
                    for (let i = 0; i < this._enabledPanels.length - 1; i++) {
×
UNCOV
333
                        this._enabledPanels[i].collapse();
×
334
                    }
UNCOV
335
                    this._enabledPanels[this._enabledPanels.length - 1].expand();
×
UNCOV
336
                    return;
×
337
                }
UNCOV
338
                this._enabledPanels.forEach(p => p.expand());
×
339
            }
340
        }
341
    }
342

343
    private getNextPanel(panel: IgxExpansionPanelComponent, dir: 1 | -1 = 1): IgxExpansionPanelComponent {
×
UNCOV
344
        const panelIndex = this._enabledPanels.indexOf(panel);
×
UNCOV
345
        return this._enabledPanels[panelIndex + dir] || panel;
×
346
    }
347

348
    private subToChanges(): void {
UNCOV
349
        this._unsubChildren$.next();
×
UNCOV
350
        this._panels.forEach(panel => {
×
UNCOV
351
            panel.contentExpanded.pipe(takeUntil(this._unsubChildren$)).subscribe((args: IExpansionPanelEventArgs) => {
×
UNCOV
352
                this._expandedPanels.add(args.owner);
×
UNCOV
353
                this._expandingPanels.delete(args.owner);
×
UNCOV
354
                const evArgs: IAccordionEventArgs = { ...args, owner: this, panel: args.owner };
×
UNCOV
355
                this.panelExpanded.emit(evArgs);
×
356
            });
UNCOV
357
            panel.contentExpanding.pipe(takeUntil(this._unsubChildren$)).subscribe((args: IExpansionPanelCancelableEventArgs) => {
×
UNCOV
358
                if (args.cancel) {
×
359
                    return;
×
360
                }
UNCOV
361
                const evArgs: IAccordionCancelableEventArgs = { ...args, owner: this, panel: args.owner };
×
UNCOV
362
                this.panelExpanding.emit(evArgs);
×
UNCOV
363
                if (evArgs.cancel) {
×
364
                    args.cancel = true;
×
365
                    return;
×
366
                }
UNCOV
367
                if (this.singleBranchExpand) {
×
UNCOV
368
                    this._expandedPanels.forEach(p => {
×
UNCOV
369
                        if (!p.header.disabled) {
×
370
                            p.collapse();
×
371
                        }
372
                    });
UNCOV
373
                    this._expandingPanels.forEach(p => {
×
UNCOV
374
                        if (!p.header.disabled) {
×
UNCOV
375
                            if (!p.animationSettings.closeAnimation) {
×
376
                                p.openAnimationPlayer?.reset();
×
377
                            }
UNCOV
378
                            if (!p.animationSettings.openAnimation) {
×
379
                                p.closeAnimationPlayer?.reset();
×
380
                            }
UNCOV
381
                            p.collapse();
×
382
                        }
383
                    });
UNCOV
384
                    this._expandingPanels.add(args.owner);
×
385
                }
386
            });
UNCOV
387
            panel.contentCollapsed.pipe(takeUntil(this._unsubChildren$)).subscribe((args: IExpansionPanelEventArgs) => {
×
UNCOV
388
                this._expandedPanels.delete(args.owner);
×
UNCOV
389
                this._expandingPanels.delete(args.owner);
×
UNCOV
390
                const evArgs: IAccordionEventArgs = { ...args, owner: this, panel: args.owner };
×
UNCOV
391
                this.panelCollapsed.emit(evArgs);
×
392
            });
UNCOV
393
            panel.contentCollapsing.pipe(takeUntil(this._unsubChildren$)).subscribe((args: IExpansionPanelCancelableEventArgs) => {
×
UNCOV
394
                const evArgs: IAccordionCancelableEventArgs = { ...args, owner: this, panel: args.owner };
×
UNCOV
395
                this.panelCollapsing.emit(evArgs);
×
UNCOV
396
                if (evArgs.cancel) {
×
397
                    args.cancel = true;
×
398
                }
399
            });
UNCOV
400
            fromEvent(panel.header.innerElement, 'keydown')
×
401
                .pipe(takeUntil(this._unsubChildren$))
402
                .subscribe((e: KeyboardEvent) => {
UNCOV
403
                    this.handleKeydown(e, panel);
×
404
                });
405
        });
406
    }
407

408
    private updatePanelsAnimation(): void {
UNCOV
409
        if (this.animationSettings !== undefined) {
×
UNCOV
410
            this.panels?.forEach(panel => panel.animationSettings = this.animationSettings);
×
411
        }
412
    }
413
}
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