• 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.36
/projects/igniteui-angular/src/lib/stepper/step/step.component.ts
1
import {
2
    AfterViewInit,
3
    booleanAttribute,
4
    ChangeDetectorRef,
5
    Component,
6
    ContentChild,
7
    ElementRef,
8
    EventEmitter,
9
    forwardRef,
10
    HostBinding,
11
    HostListener,
12
    Inject,
13
    Input,
14
    OnDestroy,
15
    Output,
16
    Renderer2,
17
    TemplateRef,
18
    ViewChild
19
} from '@angular/core';
20
import { takeUntil } from 'rxjs/operators';
21
import { Direction, IgxSlideComponentBase } from '../../carousel/carousel-base';
22
import { PlatformUtil } from '../../core/utils';
23
import { ToggleAnimationPlayer, ToggleAnimationSettings } from '../../expansion-panel/toggle-animation-component';
24
import { IgxAngularAnimationService } from '../../services/animation/angular-animation-service';
25
import { AnimationService } from '../../services/animation/animation';
26
import { IgxDirectionality } from '../../services/direction/directionality';
27
import { IgxStep, IgxStepper, IgxStepperOrientation, IgxStepType, IGX_STEPPER_COMPONENT, IGX_STEP_COMPONENT, HorizontalAnimationType } from '../stepper.common';
28
import { IgxStepContentDirective, IgxStepIndicatorDirective } from '../stepper.directive';
29
import { IgxStepperService } from '../stepper.service';
30
import { IgxRippleDirective } from '../../directives/ripple/ripple.directive';
31
import { NgIf, NgClass, NgTemplateOutlet } from '@angular/common';
32

33
let NEXT_ID = 0;
2✔
34

35
/**
36
 * The IgxStepComponent is used within the `igx-stepper` element and it holds the content of each step.
37
 * It also supports custom indicators, title and subtitle.
38
 *
39
 * @igxModule IgxStepperModule
40
 *
41
 * @igxKeywords step
42
 *
43
 * @example
44
 * ```html
45
 *  <igx-stepper>
46
 *  ...
47
 *    <igx-step [active]="true" [completed]="true">
48
 *      ...
49
 *    </igx-step>
50
 *  ...
51
 *  </igx-stepper>
52
 * ```
53
 */
54
@Component({
55
    selector: 'igx-step',
56
    templateUrl: 'step.component.html',
57
    providers: [
58
        { provide: IGX_STEP_COMPONENT, useExisting: IgxStepComponent }
59
    ],
60
    imports: [NgIf, NgClass, IgxRippleDirective, NgTemplateOutlet]
61
})
62
export class IgxStepComponent extends ToggleAnimationPlayer implements IgxStep, AfterViewInit, OnDestroy, IgxSlideComponentBase {
2✔
63

64
    /**
65
     * Get/Set the `id` of the step component.
66
     * Default value is `"igx-step-0"`;
67
     * ```html
68
     * <igx-step id="my-first-step"></igx-step>
69
     * ```
70
     * ```typescript
71
     * const stepId = this.step.id;
72
     * ```
73
     */
74
    @HostBinding('attr.id')
75
    @Input()
UNCOV
76
    public id = `igx-step-${NEXT_ID++}`;
×
77

78
    /**
79
     * Get/Set whether the step is interactable.
80
     *
81
     * ```html
82
     * <igx-stepper>
83
     * ...
84
     *     <igx-step [disabled]="true"></igx-step>
85
     * ...
86
     * </igx-stepper>
87
     * ```
88
     *
89
     * ```typescript
90
     * this.stepper.steps[1].disabled = true;
91
     * ```
92
     */
93
    @Input({ transform: booleanAttribute })
94
    public set disabled(value: boolean) {
UNCOV
95
        this._disabled = value;
×
UNCOV
96
        if (this.stepper.linear) {
×
UNCOV
97
            this.stepperService.calculateLinearDisabledSteps();
×
98
        }
99
    }
100

101
    public get disabled(): boolean {
UNCOV
102
        return this._disabled;
×
103
    }
104

105
    /**
106
     * Get/Set whether the step is completed.
107
     *
108
     * @remarks
109
     * When set to `true` the following separator is styled `solid`.
110
     *
111
     * ```html
112
     * <igx-stepper>
113
     * ...
114
     *     <igx-step [completed]="true"></igx-step>
115
     * ...
116
     * </igx-stepper>
117
     * ```
118
     *
119
     * ```typescript
120
     * this.stepper.steps[1].completed = true;
121
     * ```
122
     */
123
    @Input({ transform: booleanAttribute })
124
    @HostBinding('class.igx-stepper__step--completed')
UNCOV
125
    public completed = false;
×
126

127
    /**
128
     * Get/Set whether the step is valid.
129
     *```html
130
     * <igx-step [isValid]="form.form.valid">
131
     *      ...
132
     *      <div igxStepContent>
133
     *          <form #form="ngForm">
134
     *              ...
135
     *          </form>
136
     *      </div>
137
     * </igx-step>
138
     * ```
139
     */
140
    @Input({ transform: booleanAttribute })
141
    public get isValid(): boolean {
UNCOV
142
        return this._valid;
×
143
    }
144

145
    public set isValid(value: boolean) {
UNCOV
146
        this._valid = value;
×
UNCOV
147
        if (this.stepper.linear && this.index !== undefined) {
×
UNCOV
148
            this.stepperService.calculateLinearDisabledSteps();
×
149
        }
150
    }
151

152
    /**
153
     * Get/Set whether the step is optional.
154
     *
155
     * @remarks
156
     * Optional steps validity does not affect the default behavior when the stepper is in linear mode i.e.
157
     * if optional step is invalid the user could still move to the next step.
158
     *
159
     * ```html
160
     * <igx-step [optional]="true"></igx-step>
161
     * ```
162
     * ```typescript
163
     * this.stepper.steps[1].optional = true;
164
     * ```
165
     */
166
    @Input({ transform: booleanAttribute })
UNCOV
167
    public optional = false;
×
168

169
    /**
170
     * Get/Set the active state of the step
171
     *
172
     * ```html
173
     * <igx-step [active]="true"></igx-step>
174
     * ```
175
     *
176
     * ```typescript
177
     * this.stepper.steps[1].active = true;
178
     * ```
179
     *
180
     * @param value: boolean
181
     */
182
    @HostBinding('attr.aria-selected')
183
    @Input({ transform: booleanAttribute })
184
    public set active(value: boolean) {
UNCOV
185
        if (value) {
×
UNCOV
186
            this.stepperService.expandThroughApi(this);
×
187
        } else {
UNCOV
188
            this.stepperService.collapse(this);
×
189
        }
190
    }
191

192
    public get active(): boolean {
UNCOV
193
        return this.stepperService.activeStep === this;
×
194
    }
195

196
    /** @hidden @internal */
197
    @HostBinding('attr.tabindex')
198
    @Input()
199
    public set tabIndex(value: number) {
UNCOV
200
        this._tabIndex = value;
×
201
    }
202

203
    public get tabIndex(): number {
UNCOV
204
        return this._tabIndex;
×
205
    }
206

207
    /** @hidden @internal **/
208
    @HostBinding('attr.role')
UNCOV
209
    public role = 'tab';
×
210

211
    /** @hidden @internal */
212
    @HostBinding('attr.aria-controls')
213
    public get contentId(): string {
UNCOV
214
        return this.content?.id;
×
215
    }
216

217
    /** @hidden @internal */
218
    @HostBinding('class.igx-stepper__step')
UNCOV
219
    public cssClass = true;
×
220

221
    /** @hidden @internal */
222
    @HostBinding('class.igx-stepper__step--disabled')
223
    public get generalDisabled(): boolean {
UNCOV
224
        return this.disabled || this.linearDisabled;
×
225
    }
226

227
    /** @hidden @internal */
228
    @HostBinding('class')
229
    public get titlePositionTop(): string {
UNCOV
230
        if (this.stepper.stepType !== IgxStepType.Full) {
×
UNCOV
231
            return 'igx-stepper__step--simple';
×
232
        }
233

UNCOV
234
        return `igx-stepper__step--${this.titlePosition}`;
×
235
    }
236

237
    /**
238
     * Emitted when the step's `active` property changes. Can be used for two-way binding.
239
     *
240
     * ```html
241
     * <igx-step [(active)]="this.isActive">
242
     * </igx-step>
243
     * ```
244
     *
245
     * ```typescript
246
     * const step: IgxStepComponent = this.stepper.step[0];
247
     * step.activeChange.subscribe((e: boolean) => console.log("Step active state change to ", e))
248
     * ```
249
     */
250
    @Output()
UNCOV
251
    public activeChange = new EventEmitter<boolean>();
×
252

253
    /** @hidden @internal */
254
    @ViewChild('contentTemplate', { static: true })
255
    public contentTemplate: TemplateRef<any>;
256

257
    /** @hidden @internal */
258
    @ViewChild('customIndicator', { static: true })
259
    public customIndicatorTemplate: TemplateRef<any>;
260

261
    /** @hidden @internal */
262
    @ViewChild('contentContainer')
263
    public contentContainer: ElementRef;
264

265
    /** @hidden @internal */
UNCOV
266
    @ContentChild(forwardRef(() => IgxStepIndicatorDirective))
×
267
    public indicator: IgxStepIndicatorDirective;
268

269
    /** @hidden @internal */
UNCOV
270
    @ContentChild(forwardRef(() => IgxStepContentDirective))
×
271
    public content: IgxStepContentDirective;
272

273
    /**
274
     * Get the step index inside of the stepper.
275
     *
276
     * ```typescript
277
     * const step = this.stepper.steps[1];
278
     * const stepIndex: number = step.index;
279
     * ```
280
     */
281
    public get index(): number {
UNCOV
282
        return this._index;
×
283
    }
284

285
    /** @hidden @internal */
286
    public get indicatorTemplate(): TemplateRef<any> {
UNCOV
287
        if (this.active && this.stepper.activeIndicatorTemplate) {
×
UNCOV
288
            return this.stepper.activeIndicatorTemplate;
×
289
        }
290

UNCOV
291
        if (!this.isValid && this.stepper.invalidIndicatorTemplate) {
×
UNCOV
292
            return this.stepper.invalidIndicatorTemplate;
×
293
        }
294

UNCOV
295
        if (this.completed && this.stepper.completedIndicatorTemplate) {
×
UNCOV
296
            return this.stepper.completedIndicatorTemplate;
×
297
        }
298

UNCOV
299
        if (this.indicator) {
×
UNCOV
300
            return this.customIndicatorTemplate;
×
301
        }
302

UNCOV
303
        return null;
×
304
    }
305

306
    /** @hidden @internal */
307
    public get direction(): Direction {
UNCOV
308
        return this.stepperService.previousActiveStep
×
309
            && this.stepperService.previousActiveStep.index > this.index
310
            ? Direction.PREV
311
            : Direction.NEXT;
312
    }
313

314
    /** @hidden @internal */
315
    public get isAccessible(): boolean {
UNCOV
316
        return !this.disabled && !this.linearDisabled;
×
317
    }
318

319
    /** @hidden @internal */
320
    public get isHorizontal(): boolean {
UNCOV
321
        return this.stepper.orientation === IgxStepperOrientation.Horizontal;
×
322
    }
323

324
    /** @hidden @internal */
325
    public get isTitleVisible(): boolean {
UNCOV
326
        return this.stepper.stepType !== IgxStepType.Indicator;
×
327
    }
328

329
    /** @hidden @internal */
330
    public get isIndicatorVisible(): boolean {
UNCOV
331
        return this.stepper.stepType !== IgxStepType.Title;
×
332
    }
333

334
    /** @hidden @internal */
335
    public get titlePosition(): string {
UNCOV
336
        return this.stepper.titlePosition ? this.stepper.titlePosition : this.stepper._defaultTitlePosition;
×
337
    }
338

339
    /** @hidden @internal */
340
    public get linearDisabled(): boolean {
UNCOV
341
        return this.stepperService.linearDisabledSteps.has(this);
×
342
    }
343

344
    /** @hidden @internal */
345
    public get collapsing(): boolean {
UNCOV
346
        return this.stepperService.collapsingSteps.has(this);
×
347
    }
348

349
    /** @hidden @internal */
350
    public override get animationSettings(): ToggleAnimationSettings {
UNCOV
351
        return this.stepper.verticalAnimationSettings;
×
352
    }
353

354
    /** @hidden @internal */
355
    public get contentClasses(): any {
UNCOV
356
        if (this.isHorizontal) {
×
UNCOV
357
            return { 'igx-stepper__body-content': true, 'igx-stepper__body-content--active': this.active };
×
358
        } else {
UNCOV
359
            return 'igx-stepper__step-content';
×
360
        }
361
    }
362

363
    /** @hidden @internal */
364
    public get stepHeaderClasses(): any {
UNCOV
365
        return {
×
366
            'igx-stepper__step--optional': this.optional,
367
            'igx-stepper__step-header--current': this.active,
368
            'igx-stepper__step-header--invalid': !this.isValid
×
369
                && this.stepperService.visitedSteps.has(this) && !this.active && this.isAccessible
370
        };
371
    }
372

373
    /** @hidden @internal */
374
    public get nativeElement(): HTMLElement {
UNCOV
375
        return this.element.nativeElement;
×
376
    }
377
    /** @hidden @internal */
378
    public previous: boolean;
379
    /** @hidden @internal */
380
    public _index: number;
UNCOV
381
    private _tabIndex = -1;
×
UNCOV
382
    private _valid = true;
×
UNCOV
383
    private _focused = false;
×
UNCOV
384
    private _disabled = false;
×
385

386
    constructor(
UNCOV
387
        @Inject(IGX_STEPPER_COMPONENT) public stepper: IgxStepper,
×
UNCOV
388
        public cdr: ChangeDetectorRef,
×
UNCOV
389
        public renderer: Renderer2,
×
UNCOV
390
        protected platform: PlatformUtil,
×
UNCOV
391
        protected stepperService: IgxStepperService,
×
392
        @Inject(IgxAngularAnimationService) animationService: AnimationService,
UNCOV
393
        private element: ElementRef<HTMLElement>,
×
UNCOV
394
        private dir: IgxDirectionality
×
395
    ) {
UNCOV
396
        super(animationService);
×
397
    }
398

399
    /** @hidden @internal */
400
    @HostListener('focus')
401
    public onFocus(): void {
UNCOV
402
        this._focused = true;
×
UNCOV
403
        this.stepperService.focusedStep = this;
×
UNCOV
404
        if (this.stepperService.focusedStep !== this.stepperService.activeStep) {
×
UNCOV
405
            this.stepperService.activeStep.tabIndex = -1;
×
406
        }
407
    }
408

409
    /** @hidden @internal */
410
    @HostListener('blur')
411
    public onBlur(): void {
UNCOV
412
        this._focused = false;
×
UNCOV
413
        this.stepperService.activeStep.tabIndex = 0;
×
414
    }
415

416
    /** @hidden @internal */
417
    @HostListener('keydown', ['$event'])
418
    public handleKeydown(event: KeyboardEvent): void {
UNCOV
419
        if (!this._focused) {
×
420
            return;
×
421
        }
UNCOV
422
        const key = event.key;
×
UNCOV
423
        if (this.stepper.orientation === IgxStepperOrientation.Horizontal) {
×
UNCOV
424
            if (key === this.platform.KEYMAP.ARROW_UP || key === this.platform.KEYMAP.ARROW_DOWN) {
×
425
                return;
×
426
            }
427
        }
UNCOV
428
        if (!(this.platform.isNavigationKey(key) || this.platform.isActivationKey(event))) {
×
429
            return;
×
430
        }
UNCOV
431
        event.preventDefault();
×
UNCOV
432
        this.handleNavigation(key);
×
433
    }
434

435
    /** @hidden @internal */
436
    public ngAfterViewInit(): void {
UNCOV
437
        this.openAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(
×
438
            () => {
UNCOV
439
                if (this.stepperService.activeStep === this) {
×
UNCOV
440
                    this.stepper.activeStepChanged.emit({ owner: this.stepper, index: this.index });
×
441
                }
442
            }
443
        );
UNCOV
444
        this.closeAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
445
            this.stepperService.collapse(this);
×
UNCOV
446
            this.cdr.markForCheck();
×
447
        });
448
    }
449

450
    /** @hidden @internal */
451
    public onPointerDown(event: MouseEvent): void {
452
        event.stopPropagation();
×
453
        if (this.isHorizontal) {
×
454
            this.changeHorizontalActiveStep();
×
455
        } else {
456
            this.changeVerticalActiveStep();
×
457
        }
458
    }
459

460
    /** @hidden @internal */
461
    public handleNavigation(key: string): void {
UNCOV
462
        switch (key) {
×
463
            case this.platform.KEYMAP.HOME:
UNCOV
464
                this.stepper.steps.filter(s => s.isAccessible)[0]?.nativeElement.focus();
×
UNCOV
465
                break;
×
466
            case this.platform.KEYMAP.END:
UNCOV
467
                this.stepper.steps.filter(s => s.isAccessible).pop()?.nativeElement.focus();
×
UNCOV
468
                break;
×
469
            case this.platform.KEYMAP.ARROW_UP:
UNCOV
470
                this.previousStep?.nativeElement.focus();
×
UNCOV
471
                break;
×
472
            case this.platform.KEYMAP.ARROW_LEFT:
UNCOV
473
                if (this.dir.rtl && this.stepper.orientation === IgxStepperOrientation.Horizontal) {
×
474
                    this.nextStep?.nativeElement.focus();
×
475
                } else {
UNCOV
476
                    this.previousStep?.nativeElement.focus();
×
477
                }
UNCOV
478
                break;
×
479
            case this.platform.KEYMAP.ARROW_DOWN:
UNCOV
480
                this.nextStep?.nativeElement.focus();
×
UNCOV
481
                break;
×
482
            case this.platform.KEYMAP.ARROW_RIGHT:
UNCOV
483
                if (this.dir.rtl && this.stepper.orientation === IgxStepperOrientation.Horizontal) {
×
484
                    this.previousStep?.nativeElement.focus();
×
485
                } else {
UNCOV
486
                    this.nextStep?.nativeElement.focus();
×
487
                }
UNCOV
488
                break;
×
489
            case this.platform.KEYMAP.SPACE:
490
            case this.platform.KEYMAP.ENTER:
UNCOV
491
                if (this.isHorizontal) {
×
UNCOV
492
                    this.changeHorizontalActiveStep();
×
493
                } else {
494
                    this.changeVerticalActiveStep();
×
495
                }
UNCOV
496
                break;
×
497
            default:
498
                return;
×
499
        }
500
    }
501

502
    /** @hidden @internal */
503
    public changeHorizontalActiveStep(): void {
UNCOV
504
        if (this.stepper.animationType === HorizontalAnimationType.none && this.stepperService.activeStep !== this) {
×
UNCOV
505
            const argsCanceled = this.stepperService.emitActivatingEvent(this);
×
UNCOV
506
            if (argsCanceled) {
×
507
                return;
×
508
            }
509

UNCOV
510
            this.active = true;
×
UNCOV
511
            this.stepper.activeStepChanged.emit({ owner: this.stepper, index: this.index });
×
UNCOV
512
            return;
×
513
        }
UNCOV
514
        this.stepperService.expand(this);
×
UNCOV
515
        if (this.stepper.animationType === HorizontalAnimationType.fade) {
×
UNCOV
516
            if (this.stepperService.collapsingSteps.has(this.stepperService.previousActiveStep)) {
×
UNCOV
517
                this.stepperService.previousActiveStep.active = false;
×
518
            }
519
        }
520
    }
521

522
    private get nextStep(): IgxStepComponent | null {
UNCOV
523
        const focusedStep = this.stepperService.focusedStep;
×
UNCOV
524
        if (focusedStep) {
×
UNCOV
525
            if (focusedStep.index === this.stepper.steps.length - 1) {
×
526
                return this.stepper.steps.find(s => s.isAccessible);
×
527
            }
528

UNCOV
529
            const nextAccessible = this.stepper.steps.find((s, i) => i > focusedStep.index && s.isAccessible);
×
UNCOV
530
            return nextAccessible ? nextAccessible : this.stepper.steps.find(s => s.isAccessible);
×
531
        }
532

533
        return null;
×
534
    }
535

536
    private get previousStep(): IgxStepComponent | null {
UNCOV
537
        const focusedStep = this.stepperService.focusedStep;
×
UNCOV
538
        if (focusedStep) {
×
UNCOV
539
            if (focusedStep.index === 0) {
×
540
                return this.stepper.steps.filter(s => s.isAccessible).pop();
×
541
            }
542

543
            let prevStep;
UNCOV
544
            for (let i = focusedStep.index - 1; i >= 0; i--) {
×
UNCOV
545
                const step = this.stepper.steps[i];
×
UNCOV
546
                if (step.isAccessible) {
×
UNCOV
547
                    prevStep = step;
×
UNCOV
548
                    break;
×
549
                }
550
            }
551

UNCOV
552
            return prevStep ? prevStep : this.stepper.steps.filter(s => s.isAccessible).pop();
×
553

554
        }
555

556
        return null;
×
557
    }
558

559
    private changeVerticalActiveStep(): void {
560
        this.stepperService.expand(this);
×
561

562
        if (!this.animationSettings.closeAnimation) {
×
563
            this.stepperService.previousActiveStep?.openAnimationPlayer?.finish();
×
564
        }
565

566
        if (!this.animationSettings.openAnimation) {
×
567
            this.stepperService.activeStep.closeAnimationPlayer?.finish();
×
568
        }
569
    }
570
}
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