• 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

0.84
/projects/igniteui-angular/src/lib/stepper/stepper.component.ts
1
import { AnimationReferenceMetadata, useAnimation } from '@angular/animations';
2
import { NgIf, NgTemplateOutlet, NgFor } from '@angular/common';
3
import {
4
    AfterContentInit, ChangeDetectorRef, Component, ContentChild, ContentChildren,
5
    ElementRef, EventEmitter, HostBinding, Inject, Input, OnChanges, OnDestroy,
6
    OnInit, Output, QueryList, SimpleChanges, TemplateRef, booleanAttribute
7
} from '@angular/core';
8
import { Subject } from 'rxjs';
9
import { takeUntil } from 'rxjs/operators';
10
import { IgxCarouselComponentBase } from '../carousel/carousel-base';
11

12
import { ToggleAnimationSettings } from '../expansion-panel/toggle-animation-component';
13
import { IgxAngularAnimationService } from '../services/animation/angular-animation-service';
14
import { AnimationService } from '../services/animation/animation';
15
import { IgxStepComponent } from './step/step.component';
16
import {
17
    IgxStepper, IgxStepperOrientation, IgxStepperTitlePosition, IgxStepType,
18
    IGX_STEPPER_COMPONENT, IStepChangedEventArgs, IStepChangingEventArgs, VerticalAnimationType,
19
    HorizontalAnimationType
20
} from './stepper.common';
21
import {
22
    IgxStepActiveIndicatorDirective,
23
    IgxStepCompletedIndicatorDirective,
24
    IgxStepInvalidIndicatorDirective
25
} from './stepper.directive';
26
import { IgxStepperService } from './stepper.service';
27
import { fadeIn, growVerIn, growVerOut } from 'igniteui-angular/animations';
28

29

30
// TODO: common interface between IgxCarouselComponentBase and ToggleAnimationPlayer?
31

32
/**
33
 * IgxStepper provides a wizard-like workflow by dividing content into logical steps.
34
 *
35
 * @igxModule IgxStepperModule
36
 *
37
 * @igxKeywords stepper
38
 *
39
 * @igxGroup Layouts
40
 *
41
 * @remarks
42
 * The Ignite UI for Angular Stepper component allows the user to navigate between multiple steps.
43
 * It supports horizontal and vertical orientation as well as keyboard navigation and provides API methods to control the active step.
44
 * The component offers keyboard navigation and API to control the active step.
45
 *
46
 * @example
47
 * ```html
48
 * <igx-stepper>
49
 *  <igx-step [active]="true">
50
 *      <igx-icon igxStepIndicator>home</igx-icon>
51
 *      <p igxStepTitle>Home</p>
52
 *      <div igxStepContent>
53
 *         ...
54
 *      </div>
55
 *  </igx-step>
56
 *  <igx-step [optional]="true">
57
 *      <div igxStepContent>
58
 *          ...
59
 *      </div>
60
 *  </igx-step>
61
 *  <igx-step>
62
 *      <div igxStepContent>
63
 *          ...
64
 *      </div>
65
 *  </igx-step>
66
 * </igx-stepper>
67
 * ```
68
 */
69
@Component({
70
    selector: 'igx-stepper',
71
    templateUrl: 'stepper.component.html',
72
    providers: [
73
        IgxStepperService,
74
        { provide: IGX_STEPPER_COMPONENT, useExisting: IgxStepperComponent },
75
    ],
76
    imports: [NgIf, NgTemplateOutlet, NgFor]
77
})
78
export class IgxStepperComponent extends IgxCarouselComponentBase implements IgxStepper, OnChanges, OnInit, AfterContentInit, OnDestroy {
2✔
79

80
    /**
81
     * Get/Set the animation type of the stepper when the orientation direction is vertical.
82
     *
83
     * @remarks
84
     * Default value is `grow`. Other possible values are `fade` and `none`.
85
     *
86
     * ```html
87
     * <igx-stepper verticalAnimationType="none">
88
     * <igx-stepper>
89
     * ```
90
     */
91
    @Input()
92
    public get verticalAnimationType(): VerticalAnimationType {
93
        return this._verticalAnimationType;
×
94
    }
95

96
    public set verticalAnimationType(value: VerticalAnimationType) {
97
        // TODO: activeChange event is not emitted for the collapsing steps (loop through collapsing steps and emit)
UNCOV
98
        this.stepperService.collapsingSteps.clear();
×
UNCOV
99
        this._verticalAnimationType = value;
×
100

UNCOV
101
        switch (value) {
×
102
            case 'grow':
UNCOV
103
                this.verticalAnimationSettings = this.updateVerticalAnimationSettings(growVerIn, growVerOut);
×
UNCOV
104
                break;
×
105
            case 'fade':
UNCOV
106
                this.verticalAnimationSettings = this.updateVerticalAnimationSettings(fadeIn, null);
×
UNCOV
107
                break;
×
108
            case 'none':
UNCOV
109
                this.verticalAnimationSettings = this.updateVerticalAnimationSettings(null, null);
×
UNCOV
110
                break;
×
111
        }
112
    }
113

114
    /**
115
     * Get/Set the animation type of the stepper when the orientation direction is horizontal.
116
     *
117
     * @remarks
118
     * Default value is `grow`. Other possible values are `fade` and `none`.
119
     *
120
     * ```html
121
     * <igx-stepper animationType="none">
122
     * <igx-stepper>
123
     * ```
124
     */
125
    @Input()
126
    public get horizontalAnimationType(): HorizontalAnimationType {
127
        return this.animationType;
×
128
    }
129

130
    public set horizontalAnimationType(value: HorizontalAnimationType) {
131
        // TODO: activeChange event is not emitted for the collapsing steps (loop through collapsing steps and emit)
UNCOV
132
        this.stepperService.collapsingSteps.clear();
×
UNCOV
133
        this.animationType = value;
×
134
    }
135

136
    /**
137
     * Get/Set the animation duration.
138
     * ```html
139
     * <igx-stepper [animationDuration]="500">
140
     * <igx-stepper>
141
     * ```
142
     */
143
    @Input()
144
    public get animationDuration(): number {
UNCOV
145
        return this.defaultAnimationDuration;
×
146
    }
147

148
    public set animationDuration(value: number) {
UNCOV
149
        if (value && value > 0) {
×
UNCOV
150
            this.defaultAnimationDuration = value;
×
UNCOV
151
            return;
×
152
        }
UNCOV
153
        this.defaultAnimationDuration = this._defaultAnimationDuration;
×
154
    }
155

156
    /**
157
     * Get/Set whether the stepper is linear.
158
     *
159
     * @remarks
160
     * If the stepper is in linear mode and if the active step is valid only then the user is able to move forward.
161
     *
162
     * ```html
163
     * <igx-stepper [linear]="true"></igx-stepper>
164
     * ```
165
     */
166
    @Input({ transform: booleanAttribute })
167
    public get linear(): boolean {
UNCOV
168
        return this._linear;
×
169
    }
170

171
    public set linear(value: boolean) {
UNCOV
172
        this._linear = value;
×
UNCOV
173
        if (this._linear && this.steps.length > 0) {
×
174
            // when the stepper is in linear mode we should calculate which steps should be disabled
175
            // and which are visited i.e. their validity should be correctly displayed.
UNCOV
176
            this.stepperService.calculateVisitedSteps();
×
UNCOV
177
            this.stepperService.calculateLinearDisabledSteps();
×
178
        } else {
UNCOV
179
            this.stepperService.linearDisabledSteps.clear();
×
180
        }
181
    }
182

183
    /**
184
     * Get/Set the stepper orientation.
185
     *
186
     * ```typescript
187
     * this.stepper.orientation = IgxStepperOrientation.Vertical;
188
     * ```
189
     */
190
    @HostBinding('attr.aria-orientation')
191
    @Input()
192
    public get orientation(): IgxStepperOrientation {
UNCOV
193
        return this._orientation;
×
194
    }
195

196
    public set orientation(value: IgxStepperOrientation) {
UNCOV
197
        if (this._orientation === value) {
×
UNCOV
198
            return;
×
199
        }
200

201
        // TODO: activeChange event is not emitted for the collapsing steps
UNCOV
202
        this.stepperService.collapsingSteps.clear();
×
UNCOV
203
        this._orientation = value;
×
UNCOV
204
        this._defaultTitlePosition = this._orientation === IgxStepperOrientation.Horizontal ?
×
205
            IgxStepperTitlePosition.Bottom : IgxStepperTitlePosition.End;
206
    }
207

208
    /**
209
     * Get/Set the type of the steps.
210
     *
211
     * ```typescript
212
     * this.stepper.stepType = IgxStepType.Indicator;
213
     * ```
214
     */
215
    @Input()
UNCOV
216
    public stepType: IgxStepType = IgxStepType.Full;
×
217

218
    /**
219
     * Get/Set whether the content is displayed above the steps.
220
     *
221
     * @remarks
222
     * Default value is `false` and the content is below the steps.
223
     *
224
     * ```typescript
225
     * this.stepper.contentTop = true;
226
     * ```
227
     */
228
    @Input({ transform: booleanAttribute })
UNCOV
229
    public contentTop = false;
×
230

231
    /**
232
     * Get/Set the position of the steps title.
233
     *
234
     * @remarks
235
     * The default value when the stepper is horizontally orientated is `bottom`.
236
     * In vertical layout the default title position is `end`.
237
     *
238
     * ```typescript
239
     * this.stepper.titlePosition = IgxStepperTitlePosition.Top;
240
     * ```
241
     */
242
    @Input()
UNCOV
243
    public titlePosition: IgxStepperTitlePosition = null;
×
244

245
    /** @hidden @internal **/
246
    @HostBinding('class.igx-stepper')
UNCOV
247
    public cssClass = 'igx-stepper';
×
248

249
    /** @hidden @internal **/
250
    @HostBinding('attr.role')
UNCOV
251
    public role = 'tablist';
×
252

253
    /** @hidden @internal **/
254
    @HostBinding('class.igx-stepper--horizontal')
255
    public get directionClass() {
UNCOV
256
        return this.orientation === IgxStepperOrientation.Horizontal;
×
257
    }
258

259
    /**
260
     * Emitted when the stepper's active step is changing.
261
     *
262
     *```html
263
     * <igx-stepper (activeStepChanging)="handleActiveStepChanging($event)">
264
     * </igx-stepper>
265
     * ```
266
     *
267
     *```typescript
268
     * public handleActiveStepChanging(event: IStepChangingEventArgs) {
269
     *  if (event.newIndex < event.oldIndex) {
270
     *      event.cancel = true;
271
     *  }
272
     * }
273
     *```
274
     */
275
    @Output()
UNCOV
276
    public activeStepChanging = new EventEmitter<IStepChangingEventArgs>();
×
277

278
    /**
279
     * Emitted when the active step is changed.
280
     *
281
     * @example
282
     * ```
283
     * <igx-stepper (activeStepChanged)="handleActiveStepChanged($event)"></igx-stepper>
284
     * ```
285
     */
286
    @Output()
UNCOV
287
    public activeStepChanged = new EventEmitter<IStepChangedEventArgs>();
×
288

289
    /** @hidden @internal */
290
    @ContentChild(IgxStepInvalidIndicatorDirective, { read: TemplateRef })
291
    public invalidIndicatorTemplate: TemplateRef<IgxStepInvalidIndicatorDirective>;
292

293
    /** @hidden @internal */
294
    @ContentChild(IgxStepCompletedIndicatorDirective, { read: TemplateRef })
295
    public completedIndicatorTemplate: TemplateRef<IgxStepCompletedIndicatorDirective>;
296

297
    /** @hidden @internal */
298
    @ContentChild(IgxStepActiveIndicatorDirective, { read: TemplateRef })
299
    public activeIndicatorTemplate: TemplateRef<IgxStepActiveIndicatorDirective>;
300

301
    /** @hidden @internal */
302
    @ContentChildren(IgxStepComponent, { descendants: false })
303
    private _steps: QueryList<IgxStepComponent>;
304

305
    /**
306
     * Get all steps.
307
     *
308
     * ```typescript
309
     * const steps: IgxStepComponent[] = this.stepper.steps;
310
     * ```
311
     */
312
    public get steps(): IgxStepComponent[] {
UNCOV
313
        return this._steps?.toArray() || [];
×
314
    }
315

316
    /** @hidden @internal */
317
    public get nativeElement(): HTMLElement {
UNCOV
318
        return this.element.nativeElement;
×
319
    }
320

321
    /** @hidden @internal */
UNCOV
322
    public verticalAnimationSettings: ToggleAnimationSettings = {
×
323
        openAnimation: growVerIn,
324
        closeAnimation: growVerOut,
325
    };
326
    /** @hidden @internal */
UNCOV
327
    public _defaultTitlePosition: IgxStepperTitlePosition = IgxStepperTitlePosition.Bottom;
×
UNCOV
328
    private destroy$ = new Subject<void>();
×
UNCOV
329
    private _orientation: IgxStepperOrientation = IgxStepperOrientation.Horizontal;
×
UNCOV
330
    private _verticalAnimationType: VerticalAnimationType = VerticalAnimationType.Grow;
×
UNCOV
331
    private _linear = false;
×
UNCOV
332
    private readonly _defaultAnimationDuration = 350;
×
333

334
    constructor(
335
        cdr: ChangeDetectorRef,
336
        @Inject(IgxAngularAnimationService) animationService: AnimationService,
UNCOV
337
        private stepperService: IgxStepperService,
×
UNCOV
338
        private element: ElementRef<HTMLElement>) {
×
UNCOV
339
        super(animationService, cdr);
×
UNCOV
340
        this.stepperService.stepper = this;
×
341
    }
342

343
    /** @hidden @internal */
344
    public ngOnChanges(changes: SimpleChanges): void {
UNCOV
345
        if (changes['animationDuration']) {
×
UNCOV
346
            this.verticalAnimationType = this._verticalAnimationType;
×
347
        }
348
    }
349

350
    /** @hidden @internal */
351
    public ngOnInit(): void {
UNCOV
352
        this.enterAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
353
            this.activeStepChanged.emit({ owner: this, index: this.stepperService.activeStep.index });
×
354
        });
UNCOV
355
        this.leaveAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
356
            if (this.stepperService.collapsingSteps.size === 1) {
×
UNCOV
357
                this.stepperService.collapse(this.stepperService.previousActiveStep);
×
358
            } else {
UNCOV
359
                Array.from(this.stepperService.collapsingSteps).slice(0, this.stepperService.collapsingSteps.size - 1)
×
UNCOV
360
                    .forEach(step => this.stepperService.collapse(step));
×
361
            }
362
        });
363

364

365
    }
366

367
    /** @hidden @internal */
368
    public ngAfterContentInit(): void {
369
        let activeStep;
UNCOV
370
        this.steps.forEach((step, index) => {
×
UNCOV
371
            this.updateStepAria(step, index);
×
UNCOV
372
            if (!activeStep && step.active) {
×
UNCOV
373
                activeStep = step;
×
374
            }
375
        });
UNCOV
376
        if (!activeStep) {
×
UNCOV
377
            this.activateFirstStep(true);
×
378
        }
379

UNCOV
380
        if (this.linear) {
×
UNCOV
381
            this.stepperService.calculateLinearDisabledSteps();
×
382
        }
383

UNCOV
384
        this.handleStepChanges();
×
385
    }
386

387
    /** @hidden @internal */
388
    public ngOnDestroy(): void {
UNCOV
389
        this.destroy$.next();
×
UNCOV
390
        this.destroy$.complete();
×
391
    }
392

393
    /**
394
     * Activates the step at a given index.
395
     *
396
     *```typescript
397
     * this.stepper.navigateTo(1);
398
     *```
399
     */
400
    public navigateTo(index: number): void {
UNCOV
401
        const step = this.steps[index];
×
UNCOV
402
        if (!step || this.stepperService.activeStep === step) {
×
UNCOV
403
            return;
×
404
        }
UNCOV
405
        this.activateStep(step);
×
406
    }
407

408
    /**
409
     * Activates the next enabled step.
410
     *
411
     *```typescript
412
     * this.stepper.next();
413
     *```
414
     */
415
    public next(): void {
UNCOV
416
        this.moveToNextStep();
×
417
    }
418

419
    /**
420
     * Activates the previous enabled step.
421
     *
422
     *```typescript
423
     * this.stepper.prev();
424
     *```
425
     */
426
    public prev(): void {
427
        this.moveToNextStep(false);
×
428
    }
429

430
    /**
431
     * Resets the stepper to its initial state i.e. activates the first step.
432
     *
433
     * @remarks
434
     * The steps' content will not be automatically reset.
435
     *```typescript
436
     * this.stepper.reset();
437
     *```
438
     */
439
    public reset(): void {
UNCOV
440
        this.stepperService.visitedSteps.clear();
×
UNCOV
441
        const activeStep = this.steps.find(s => !s.disabled);
×
UNCOV
442
        if (activeStep) {
×
UNCOV
443
            this.activateStep(activeStep);
×
444
        }
445
    }
446

447
    /** @hidden @internal */
448
    public playHorizontalAnimations(): void {
UNCOV
449
        this.previousItem = this.stepperService.previousActiveStep;
×
UNCOV
450
        this.currentItem = this.stepperService.activeStep;
×
UNCOV
451
        this.triggerAnimations();
×
452
    }
453

454
    protected getPreviousElement(): HTMLElement {
UNCOV
455
        return this.stepperService.previousActiveStep?.contentContainer.nativeElement;
×
456
    }
457

458
    protected getCurrentElement(): HTMLElement {
UNCOV
459
        return this.stepperService.activeStep.contentContainer.nativeElement;
×
460
    }
461

462
    private updateVerticalAnimationSettings(
463
        openAnimation: AnimationReferenceMetadata,
464
        closeAnimation: AnimationReferenceMetadata): ToggleAnimationSettings {
UNCOV
465
        const customCloseAnimation = useAnimation(closeAnimation, {
×
466
            params: {
467
                duration: this.animationDuration + 'ms'
468
            }
469
        });
UNCOV
470
        const customOpenAnimation = useAnimation(openAnimation, {
×
471
            params: {
472
                duration: this.animationDuration + 'ms'
473
            }
474
        });
475

UNCOV
476
        return {
×
477
            openAnimation: openAnimation ? customOpenAnimation : null,
×
478
            closeAnimation: closeAnimation ? customCloseAnimation : null
×
479
        };
480
    }
481

482
    private updateStepAria(step: IgxStepComponent, index: number): void {
UNCOV
483
        step._index = index;
×
UNCOV
484
        step.renderer.setAttribute(step.nativeElement, 'aria-setsize', (this.steps.length).toString());
×
UNCOV
485
        step.renderer.setAttribute(step.nativeElement, 'aria-posinset', (index + 1).toString());
×
486
    }
487

488
    private handleStepChanges(): void {
UNCOV
489
        this._steps.changes.pipe(takeUntil(this.destroy$)).subscribe(steps => {
×
UNCOV
490
            Promise.resolve().then(() => {
×
UNCOV
491
                steps.forEach((step, index) => {
×
UNCOV
492
                    this.updateStepAria(step, index);
×
493
                });
494

495
                // when the active step is removed
UNCOV
496
                const hasActiveStep = this.steps.find(s => s === this.stepperService.activeStep);
×
UNCOV
497
                if (!hasActiveStep) {
×
UNCOV
498
                    this.activateFirstStep();
×
499
                }
500
                // TO DO: mark step added before the active as visited?
UNCOV
501
                if (this.linear) {
×
UNCOV
502
                    this.stepperService.calculateLinearDisabledSteps();
×
503
                }
504
            });
505
        });
506
    }
507

508
    private activateFirstStep(activateInitially = false) {
×
UNCOV
509
        const firstEnabledStep = this.steps.find(s => !s.disabled);
×
UNCOV
510
        if (firstEnabledStep) {
×
UNCOV
511
            firstEnabledStep.active = true;
×
UNCOV
512
            if (activateInitially) {
×
UNCOV
513
                firstEnabledStep.activeChange.emit(true);
×
UNCOV
514
                this.activeStepChanged.emit({ owner: this, index: firstEnabledStep.index });
×
515
            }
516
        }
517
    }
518

519
    private activateStep(step: IgxStepComponent) {
UNCOV
520
        if (this.orientation === IgxStepperOrientation.Horizontal) {
×
UNCOV
521
            step.changeHorizontalActiveStep();
×
522
        } else {
UNCOV
523
            this.stepperService.expand(step);
×
524
        }
525
    }
526

527
    private moveToNextStep(next = true) {
×
UNCOV
528
        let steps: IgxStepComponent[] = this.steps;
×
UNCOV
529
        let activeStepIndex = this.stepperService.activeStep.index;
×
UNCOV
530
        if (!next) {
×
531
            steps = this.steps.reverse();
×
532
            activeStepIndex = steps.findIndex(s => s === this.stepperService.activeStep);
×
533
        }
534

UNCOV
535
        const nextStep = steps.find((s, i) => i > activeStepIndex && s.isAccessible);
×
UNCOV
536
        if (nextStep) {
×
UNCOV
537
            this.activateStep(nextStep);
×
538
        }
539
    }
540
}
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