igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,305 lines (1,300 loc) • 60.1 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Directive, HostBinding, inject, ElementRef, Input, ChangeDetectorRef, Renderer2, EventEmitter, booleanAttribute, HostListener, forwardRef, ContentChild, ViewChild, Output, Component, TemplateRef, ContentChildren, NgModule } from '@angular/core';
import { takeUntil } from 'rxjs/operators';
import { CarouselAnimationType, CarouselAnimationDirection, IgxCarouselComponentBase } from 'igniteui-angular/carousel';
import { NgClass, NgTemplateOutlet } from '@angular/common';
import { IgxRippleDirective } from 'igniteui-angular/directives';
import { ToggleAnimationPlayer } from 'igniteui-angular/expansion-panel';
import { PlatformUtil, ɵIgxDirectionality as _IgxDirectionality } from 'igniteui-angular/core';
import { useAnimation } from '@angular/animations';
import { Subject } from 'rxjs';
import { fadeIn, growVerIn, growVerOut } from 'igniteui-angular/animations';
// Enums
const IgxStepperOrientation = {
Horizontal: 'horizontal',
Vertical: 'vertical'
};
const IgxStepType = {
Indicator: 'indicator',
Title: 'title',
Full: 'full'
};
const IgxStepperTitlePosition = {
Bottom: 'bottom',
Top: 'top',
End: 'end',
Start: 'start'
};
const VerticalAnimationType = {
Grow: 'grow',
Fade: 'fade',
None: 'none'
};
const HorizontalAnimationType = {
...CarouselAnimationType
};
// Token
const IGX_STEPPER_COMPONENT = /*@__PURE__*/ new InjectionToken('IgxStepperToken');
const IGX_STEP_COMPONENT = /*@__PURE__*/ new InjectionToken('IgxStepToken');
/** @hidden @internal */
class IgxStepperService {
constructor() {
this.collapsingSteps = new Set();
this.linearDisabledSteps = new Set();
this.visitedSteps = new Set();
}
/**
* Activates the step, fires the steps change event and plays animations.
*/
expand(step) {
if (this.activeStep === step) {
return;
}
const cancel = this.emitActivatingEvent(step);
if (cancel) {
return;
}
this.collapsingSteps.delete(step);
this.previousActiveStep = this.activeStep;
this.activeStep = step;
this.activeStep.activeChange.emit(true);
this.collapsingSteps.add(this.previousActiveStep);
this.visitedSteps.add(this.activeStep);
if (this.stepper.orientation === IgxStepperOrientation.Vertical) {
this.previousActiveStep.playCloseAnimation(this.previousActiveStep.contentContainer);
this.activeStep.cdr.detectChanges();
this.activeStep.playOpenAnimation(this.activeStep.contentContainer);
}
else {
this.activeStep.cdr.detectChanges();
this.stepper.playHorizontalAnimations();
}
}
/**
* Activates the step and fires the steps change event without playing animations.
*/
expandThroughApi(step) {
if (this.activeStep === step) {
return;
}
this.collapsingSteps.clear();
this.previousActiveStep = this.activeStep;
this.activeStep = step;
if (this.previousActiveStep) {
this.previousActiveStep.tabIndex = -1;
}
this.activeStep.tabIndex = 0;
this.visitedSteps.add(this.activeStep);
this.activeStep.cdr.markForCheck();
this.previousActiveStep?.cdr.markForCheck();
this.activeStep.activeChange.emit(true);
this.previousActiveStep?.activeChange.emit(false);
}
/**
* Collapses the currently active step and fires the change event.
*/
collapse(step) {
if (this.activeStep === step) {
return;
}
step.activeChange.emit(false);
this.collapsingSteps.delete(step);
}
/**
* Determines the steps that should be marked as visited based on the active step.
*/
calculateVisitedSteps() {
this.stepper.steps.forEach(step => {
if (step.index <= this.activeStep.index) {
this.visitedSteps.add(step);
}
else {
this.visitedSteps.delete(step);
}
});
}
/**
* Determines the steps that should be disabled in linear mode based on the validity of the active step.
*/
calculateLinearDisabledSteps() {
if (!this.activeStep) {
return;
}
if (this.activeStep.isValid) {
const firstRequiredIndex = this.getNextRequiredStep();
if (firstRequiredIndex !== -1) {
this.updateLinearDisabledSteps(firstRequiredIndex);
}
else {
this.linearDisabledSteps.clear();
}
}
else {
this.stepper.steps.forEach(s => {
if (s.index > this.activeStep.index) {
this.linearDisabledSteps.add(s);
}
});
}
}
emitActivatingEvent(step) {
const args = {
owner: this.stepper,
newIndex: step.index,
oldIndex: this.activeStep.index,
cancel: false
};
this.stepper.activeStepChanging.emit(args);
return args.cancel;
}
/**
* Updates the linearDisabled steps from the current active step to the next required invalid step.
*
* @param toIndex the index of the last step that should be enabled.
*/
updateLinearDisabledSteps(toIndex) {
this.stepper.steps.forEach(s => {
if (s.index > this.activeStep.index) {
if (s.index <= toIndex) {
this.linearDisabledSteps.delete(s);
}
else {
this.linearDisabledSteps.add(s);
}
}
});
}
getNextRequiredStep() {
if (!this.activeStep) {
return;
}
return this.stepper.steps.findIndex(s => s.index > this.activeStep.index && !s.optional && !s.disabled && !s.isValid);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepperService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepperService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepperService, decorators: [{
type: Injectable
}] });
/**
* Allows a custom element to be added as an active step indicator.
*
* @igxModule IgxStepperModule
* @igxTheme igx-stepper-theme
* @igxKeywords stepper
* @igxGroup Layouts
*
* @example
* <igx-stepper>
* <ng-template igxStepActiveIndicator>
* <igx-icon>edit</igx-icon>
* </ng-template>
* </igx-stepper>
*/
class IgxStepActiveIndicatorDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepActiveIndicatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxStepActiveIndicatorDirective, isStandalone: true, selector: "[igxStepActiveIndicator]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepActiveIndicatorDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxStepActiveIndicator]',
standalone: true
}]
}] });
/**
* Allows a custom element to be added as a complete step indicator.
*
* @igxModule IgxStepperModule
* @igxTheme igx-stepper-theme
* @igxKeywords stepper
* @igxGroup Layouts
*
* @example
* <igx-stepper>
* <ng-template igxStepCompletedIndicator>
* <igx-icon>check</igx-icon>
* </ng-template>
* </igx-stepper>
*/
class IgxStepCompletedIndicatorDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepCompletedIndicatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxStepCompletedIndicatorDirective, isStandalone: true, selector: "[igxStepCompletedIndicator]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepCompletedIndicatorDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxStepCompletedIndicator]',
standalone: true
}]
}] });
/**
* Allows a custom element to be added as an invalid step indicator.
*
* @igxModule IgxStepperModule
* @igxTheme igx-stepper-theme
* @igxKeywords stepper
* @igxGroup Layouts
*
* @example
* <igx-stepper>
* <ng-template igxStepInvalidIndicator>
* <igx-icon>error</igx-icon>
* </ng-template>
* </igx-stepper>
*/
class IgxStepInvalidIndicatorDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepInvalidIndicatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxStepInvalidIndicatorDirective, isStandalone: true, selector: "[igxStepInvalidIndicator]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepInvalidIndicatorDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxStepInvalidIndicator]',
standalone: true
}]
}] });
/**
* Allows a custom element to be added as a step indicator.
*
* @igxModule IgxStepperModule
* @igxTheme igx-stepper-theme
* @igxKeywords stepper
* @igxGroup Layouts
*
* @example
* <igx-stepper>
* <igx-step>
* <igx-icon igxStepIndicator>home</igx-icon>
* </igx-step>
* </igx-stepper>
*/
class IgxStepIndicatorDirective {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepIndicatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxStepIndicatorDirective, isStandalone: true, selector: "[igxStepIndicator]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepIndicatorDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxStepIndicator]',
standalone: true
}]
}] });
/**
* Allows a custom element to be added as a step title.
*
* @igxModule IgxStepperModule
* @igxTheme igx-stepper-theme
* @igxKeywords stepper
* @igxGroup Layouts
*
* @example
* <igx-stepper>
* <igx-step>
* <p igxStepTitle>Home</p>
* </igx-step>
* </igx-stepper>
*/
class IgxStepTitleDirective {
constructor() {
this.defaultClass = true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxStepTitleDirective, isStandalone: true, selector: "[igxStepTitle]", host: { properties: { "class.igx-stepper__step-title": "this.defaultClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepTitleDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxStepTitle]',
standalone: true
}]
}], propDecorators: { defaultClass: [{
type: HostBinding,
args: ['class.igx-stepper__step-title']
}] } });
/**
* Allows a custom element to be added as a step subtitle.
*
* @igxModule IgxStepperModule
* @igxTheme igx-stepper-theme
* @igxKeywords stepper
* @igxGroup Layouts
*
* @example
* <igx-stepper>
* <igx-step>
* <p igxStepSubtitle>Home Subtitle</p>
* </igx-step>
* </igx-stepper>
*/
class IgxStepSubtitleDirective {
constructor() {
this.defaultClass = true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepSubtitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxStepSubtitleDirective, isStandalone: true, selector: "[igxStepSubtitle]", host: { properties: { "class.igx-stepper__step-subtitle": "this.defaultClass" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepSubtitleDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxStepSubtitle]',
standalone: true
}]
}], propDecorators: { defaultClass: [{
type: HostBinding,
args: ['class.igx-stepper__step-subtitle']
}] } });
/**
* Allows a custom element to be added as a step content.
*
* @igxModule IgxStepperModule
* @igxTheme igx-stepper-theme
* @igxKeywords stepper
* @igxGroup Layouts
*
* @example
* <igx-stepper>
* <igx-step>
* <div igxStepContent>...</div>
* </igx-step>
* </igx-stepper>
*/
class IgxStepContentDirective {
constructor() {
this.step = inject(IGX_STEP_COMPONENT);
this.stepperService = inject(IgxStepperService);
this.elementRef = inject(ElementRef);
this.defaultClass = true;
this.role = 'tabpanel';
this.id = this.target.id.replace('step', 'content');
this._tabIndex = null;
}
get target() {
return this.step;
}
get stepId() {
return this.target.id;
}
get tabIndex() {
if (this._tabIndex !== null) {
return this._tabIndex;
}
return this.stepperService.activeStep === this.target ? 0 : -1;
}
set tabIndex(val) {
this._tabIndex = val;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.2", type: IgxStepContentDirective, isStandalone: true, selector: "[igxStepContent]", inputs: { id: "id", tabIndex: "tabIndex" }, host: { properties: { "class.igx-stepper__step-content": "this.defaultClass", "attr.role": "this.role", "attr.aria-labelledby": "this.stepId", "attr.id": "this.id", "attr.tabindex": "this.tabIndex" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepContentDirective, decorators: [{
type: Directive,
args: [{
selector: '[igxStepContent]',
standalone: true
}]
}], propDecorators: { defaultClass: [{
type: HostBinding,
args: ['class.igx-stepper__step-content']
}], role: [{
type: HostBinding,
args: ['attr.role']
}], stepId: [{
type: HostBinding,
args: ['attr.aria-labelledby']
}], id: [{
type: HostBinding,
args: ['attr.id']
}, {
type: Input
}], tabIndex: [{
type: HostBinding,
args: ['attr.tabindex']
}, {
type: Input
}] } });
let NEXT_ID = 0;
/**
* The IgxStepComponent is used within the `igx-stepper` element and it holds the content of each step.
* It also supports custom indicators, title and subtitle.
*
* @igxModule IgxStepperModule
*
* @igxKeywords step
*
* @example
* ```html
* <igx-stepper>
* ...
* <igx-step [active]="true" [completed]="true">
* ...
* </igx-step>
* ...
* </igx-stepper>
* ```
*/
class IgxStepComponent extends ToggleAnimationPlayer {
constructor() {
super(...arguments);
this.stepper = inject(IGX_STEPPER_COMPONENT);
this.cdr = inject(ChangeDetectorRef);
this.renderer = inject(Renderer2);
this.platform = inject(PlatformUtil);
this.stepperService = inject(IgxStepperService);
this.element = inject(ElementRef);
this.dir = inject(_IgxDirectionality);
/**
* Get/Set the `id` of the step component.
* Default value is `"igx-step-0"`;
* ```html
* <igx-step id="my-first-step"></igx-step>
* ```
* ```typescript
* const stepId = this.step.id;
* ```
*/
this.id = `igx-step-${NEXT_ID++}`;
/**
* Get/Set whether the step is completed.
*
* @remarks
* When set to `true` the following separator is styled `solid`.
*
* ```html
* <igx-stepper>
* ...
* <igx-step [completed]="true"></igx-step>
* ...
* </igx-stepper>
* ```
*
* ```typescript
* this.stepper.steps[1].completed = true;
* ```
*/
this.completed = false;
/**
* Get/Set whether the step is optional.
*
* @remarks
* Optional steps validity does not affect the default behavior when the stepper is in linear mode i.e.
* if optional step is invalid the user could still move to the next step.
*
* ```html
* <igx-step [optional]="true"></igx-step>
* ```
* ```typescript
* this.stepper.steps[1].optional = true;
* ```
*/
this.optional = false;
/** @hidden @internal **/
this.role = 'tab';
/** @hidden @internal */
this.cssClass = true;
/**
* Emitted when the step's `active` property changes. Can be used for two-way binding.
*
* ```html
* <igx-step [(active)]="this.isActive">
* </igx-step>
* ```
*
* ```typescript
* const step: IgxStepComponent = this.stepper.step[0];
* step.activeChange.subscribe((e: boolean) => console.log("Step active state change to ", e))
* ```
*/
this.activeChange = new EventEmitter();
this._tabIndex = -1;
this._valid = true;
this._focused = false;
this._disabled = false;
}
/**
* Get/Set whether the step is interactable.
*
* ```html
* <igx-stepper>
* ...
* <igx-step [disabled]="true"></igx-step>
* ...
* </igx-stepper>
* ```
*
* ```typescript
* this.stepper.steps[1].disabled = true;
* ```
*/
set disabled(value) {
this._disabled = value;
if (this.stepper.linear) {
this.stepperService.calculateLinearDisabledSteps();
}
}
get disabled() {
return this._disabled;
}
/**
* Get/Set whether the step is valid.
*```html
* <igx-step [isValid]="form.form.valid">
* ...
* <div igxStepContent>
* <form #form="ngForm">
* ...
* </form>
* </div>
* </igx-step>
* ```
*/
get isValid() {
return this._valid;
}
set isValid(value) {
this._valid = value;
if (this.stepper.linear && this.index !== undefined) {
this.stepperService.calculateLinearDisabledSteps();
}
}
/**
* Get/Set the active state of the step
*
* ```html
* <igx-step [active]="true"></igx-step>
* ```
*
* ```typescript
* this.stepper.steps[1].active = true;
* ```
*
* @param value: boolean
*/
set active(value) {
if (value) {
this.stepperService.expandThroughApi(this);
}
else {
this.stepperService.collapse(this);
}
}
get active() {
return this.stepperService.activeStep === this;
}
/** @hidden @internal */
set tabIndex(value) {
this._tabIndex = value;
}
get tabIndex() {
return this._tabIndex;
}
/** @hidden @internal */
get contentId() {
return this.content?.id;
}
/** @hidden @internal */
get generalDisabled() {
return this.disabled || this.linearDisabled;
}
/** @hidden @internal */
get titlePositionTop() {
if (this.stepper.stepType !== IgxStepType.Full) {
return 'igx-stepper__step--simple';
}
return `igx-stepper__step--${this.titlePosition}`;
}
/**
* Get the step index inside of the stepper.
*
* ```typescript
* const step = this.stepper.steps[1];
* const stepIndex: number = step.index;
* ```
*/
get index() {
return this._index;
}
/** @hidden @internal */
get indicatorTemplate() {
if (this.active && this.stepper.activeIndicatorTemplate) {
return this.stepper.activeIndicatorTemplate;
}
if (!this.isValid && this.stepper.invalidIndicatorTemplate) {
return this.stepper.invalidIndicatorTemplate;
}
if (this.completed && this.stepper.completedIndicatorTemplate) {
return this.stepper.completedIndicatorTemplate;
}
if (this.indicator) {
return this.customIndicatorTemplate;
}
return null;
}
/** @hidden @internal */
get direction() {
return this.stepperService.previousActiveStep
&& this.stepperService.previousActiveStep.index > this.index
? CarouselAnimationDirection.PREV
: CarouselAnimationDirection.NEXT;
}
/** @hidden @internal */
get isAccessible() {
return !this.disabled && !this.linearDisabled;
}
/** @hidden @internal */
get isHorizontal() {
return this.stepper.orientation === IgxStepperOrientation.Horizontal;
}
/** @hidden @internal */
get isTitleVisible() {
return this.stepper.stepType !== IgxStepType.Indicator;
}
/** @hidden @internal */
get isIndicatorVisible() {
return this.stepper.stepType !== IgxStepType.Title;
}
/** @hidden @internal */
get titlePosition() {
return this.stepper.titlePosition ? this.stepper.titlePosition : this.stepper._defaultTitlePosition;
}
/** @hidden @internal */
get linearDisabled() {
return this.stepperService.linearDisabledSteps.has(this);
}
/** @hidden @internal */
get collapsing() {
return this.stepperService.collapsingSteps.has(this);
}
/** @hidden @internal */
get animationSettings() {
return this.stepper.verticalAnimationSettings;
}
/** @hidden @internal */
get contentClasses() {
if (this.isHorizontal) {
return { 'igx-stepper__body-content': true, 'igx-stepper__body-content--active': this.active };
}
else {
return 'igx-stepper__step-content';
}
}
/** @hidden @internal */
get stepHeaderClasses() {
return {
'igx-stepper__step--optional': this.optional,
'igx-stepper__step-header--current': this.active,
'igx-stepper__step-header--invalid': !this.isValid
&& this.stepperService.visitedSteps.has(this) && !this.active && this.isAccessible
};
}
/** @hidden @internal */
get nativeElement() {
return this.element.nativeElement;
}
/** @hidden @internal */
onFocus() {
this._focused = true;
this.stepperService.focusedStep = this;
if (this.stepperService.focusedStep !== this.stepperService.activeStep) {
this.stepperService.activeStep.tabIndex = -1;
}
}
/** @hidden @internal */
onBlur() {
this._focused = false;
this.stepperService.activeStep.tabIndex = 0;
}
/** @hidden @internal */
handleKeydown(event) {
if (!this._focused) {
return;
}
const key = event.key;
if (this.stepper.orientation === IgxStepperOrientation.Horizontal) {
if (key === this.platform.KEYMAP.ARROW_UP || key === this.platform.KEYMAP.ARROW_DOWN) {
return;
}
}
if (!(this.platform.isNavigationKey(key) || this.platform.isActivationKey(event))) {
return;
}
event.preventDefault();
this.handleNavigation(key);
}
/** @hidden @internal */
ngAfterViewInit() {
this.openAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
if (this.stepperService.activeStep === this) {
this.stepper.activeStepChanged.emit({ owner: this.stepper, index: this.index });
}
});
this.closeAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.stepperService.collapse(this);
this.cdr.markForCheck();
});
}
/** @hidden @internal */
onPointerDown(event) {
event.stopPropagation();
if (this.isHorizontal) {
this.changeHorizontalActiveStep();
}
else {
this.changeVerticalActiveStep();
}
}
/** @hidden @internal */
handleNavigation(key) {
switch (key) {
case this.platform.KEYMAP.HOME:
this.stepper.steps.filter(s => s.isAccessible)[0]?.nativeElement.focus();
break;
case this.platform.KEYMAP.END:
this.stepper.steps.filter(s => s.isAccessible).pop()?.nativeElement.focus();
break;
case this.platform.KEYMAP.ARROW_UP:
this.previousStep?.nativeElement.focus();
break;
case this.platform.KEYMAP.ARROW_LEFT:
if (this.dir.rtl && this.stepper.orientation === IgxStepperOrientation.Horizontal) {
this.nextStep?.nativeElement.focus();
}
else {
this.previousStep?.nativeElement.focus();
}
break;
case this.platform.KEYMAP.ARROW_DOWN:
this.nextStep?.nativeElement.focus();
break;
case this.platform.KEYMAP.ARROW_RIGHT:
if (this.dir.rtl && this.stepper.orientation === IgxStepperOrientation.Horizontal) {
this.previousStep?.nativeElement.focus();
}
else {
this.nextStep?.nativeElement.focus();
}
break;
case this.platform.KEYMAP.SPACE:
case this.platform.KEYMAP.ENTER:
if (this.isHorizontal) {
this.changeHorizontalActiveStep();
}
else {
this.changeVerticalActiveStep();
}
break;
default:
return;
}
}
/** @hidden @internal */
changeHorizontalActiveStep() {
if (this.stepper.animationType === HorizontalAnimationType.none && this.stepperService.activeStep !== this) {
const argsCanceled = this.stepperService.emitActivatingEvent(this);
if (argsCanceled) {
return;
}
this.active = true;
this.stepper.activeStepChanged.emit({ owner: this.stepper, index: this.index });
return;
}
this.stepperService.expand(this);
if (this.stepper.animationType === HorizontalAnimationType.fade) {
if (this.stepperService.collapsingSteps.has(this.stepperService.previousActiveStep)) {
this.stepperService.previousActiveStep.active = false;
}
}
}
get nextStep() {
const focusedStep = this.stepperService.focusedStep;
if (focusedStep) {
if (focusedStep.index === this.stepper.steps.length - 1) {
return this.stepper.steps.find(s => s.isAccessible);
}
const nextAccessible = this.stepper.steps.find((s, i) => i > focusedStep.index && s.isAccessible);
return nextAccessible ? nextAccessible : this.stepper.steps.find(s => s.isAccessible);
}
return null;
}
get previousStep() {
const focusedStep = this.stepperService.focusedStep;
if (focusedStep) {
if (focusedStep.index === 0) {
return this.stepper.steps.filter(s => s.isAccessible).pop();
}
let prevStep;
for (let i = focusedStep.index - 1; i >= 0; i--) {
const step = this.stepper.steps[i];
if (step.isAccessible) {
prevStep = step;
break;
}
}
return prevStep ? prevStep : this.stepper.steps.filter(s => s.isAccessible).pop();
}
return null;
}
changeVerticalActiveStep() {
this.stepperService.expand(this);
if (!this.animationSettings.closeAnimation) {
this.stepperService.previousActiveStep?.openAnimationPlayer?.finish();
}
if (!this.animationSettings.openAnimation) {
this.stepperService.activeStep.closeAnimationPlayer?.finish();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: IgxStepComponent, isStandalone: true, selector: "igx-step", inputs: { id: "id", disabled: ["disabled", "disabled", booleanAttribute], completed: ["completed", "completed", booleanAttribute], isValid: ["isValid", "isValid", booleanAttribute], optional: ["optional", "optional", booleanAttribute], active: ["active", "active", booleanAttribute], tabIndex: "tabIndex" }, outputs: { activeChange: "activeChange" }, host: { listeners: { "focus": "onFocus()", "blur": "onBlur()", "keydown": "handleKeydown($event)" }, properties: { "attr.id": "this.id", "class.igx-stepper__step--completed": "this.completed", "attr.aria-selected": "this.active", "attr.tabindex": "this.tabIndex", "attr.role": "this.role", "attr.aria-controls": "this.contentId", "class.igx-stepper__step": "this.cssClass", "class.igx-stepper__step--disabled": "this.generalDisabled", "class": "this.titlePositionTop" } }, providers: [
{ provide: IGX_STEP_COMPONENT, useExisting: IgxStepComponent }
], queries: [{ propertyName: "indicator", first: true, predicate: i0.forwardRef(() => IgxStepIndicatorDirective), descendants: true }, { propertyName: "content", first: true, predicate: i0.forwardRef(() => IgxStepContentDirective), descendants: true }], viewQueries: [{ propertyName: "contentTemplate", first: true, predicate: ["contentTemplate"], descendants: true, static: true }, { propertyName: "customIndicatorTemplate", first: true, predicate: ["customIndicator"], descendants: true, static: true }, { propertyName: "contentContainer", first: true, predicate: ["contentContainer"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<ng-template #defaultTitle>\n @if (isTitleVisible) {\n <ng-content select=\"[igxStepTitle]\"></ng-content>\n }\n @if (isTitleVisible) {\n <ng-content select=\"[igxStepSubtitle]\"></ng-content>\n }\n</ng-template>\n\n<ng-template #contentTemplate>\n <div [ngClass]=\"contentClasses\" #contentContainer>\n @if (active || collapsing) {\n <ng-content select=\"[igxStepContent]\"></ng-content>\n }\n </div>\n</ng-template>\n\n<ng-template #defaultIndicator>\n <span>{{ index + 1 }}</span>\n</ng-template>\n\n<ng-template #customIndicator>\n <ng-content select=\"[igxStepIndicator]\"></ng-content>\n</ng-template>\n\n<div class=\"igx-stepper__step-header\" igxRipple [ngClass]=\"stepHeaderClasses\" (keydown)=\"handleKeydown($event)\"\n (click)=\"onPointerDown($event)\">\n\n @if (isIndicatorVisible) {\n <div class=\"igx-stepper__step-indicator\">\n <ng-container *ngTemplateOutlet=\"indicatorTemplate ? indicatorTemplate : defaultIndicator\"></ng-container>\n </div>\n }\n\n <div class=\"igx-stepper__step-title-wrapper\">\n <ng-container *ngTemplateOutlet=\"defaultTitle\"></ng-container>\n </div>\n</div>\n\n@if (!isHorizontal) {\n <div class=\"igx-stepper__step-content-wrapper\">\n <ng-container *ngTemplateOutlet=\"contentTemplate\"></ng-container>\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IgxRippleDirective, selector: "[igxRipple]", inputs: ["igxRippleTarget", "igxRipple", "igxRippleDuration", "igxRippleCentered", "igxRippleDisabled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxStepComponent, decorators: [{
type: Component,
args: [{ selector: 'igx-step', providers: [
{ provide: IGX_STEP_COMPONENT, useExisting: IgxStepComponent }
], imports: [NgClass, IgxRippleDirective, NgTemplateOutlet], template: "<ng-template #defaultTitle>\n @if (isTitleVisible) {\n <ng-content select=\"[igxStepTitle]\"></ng-content>\n }\n @if (isTitleVisible) {\n <ng-content select=\"[igxStepSubtitle]\"></ng-content>\n }\n</ng-template>\n\n<ng-template #contentTemplate>\n <div [ngClass]=\"contentClasses\" #contentContainer>\n @if (active || collapsing) {\n <ng-content select=\"[igxStepContent]\"></ng-content>\n }\n </div>\n</ng-template>\n\n<ng-template #defaultIndicator>\n <span>{{ index + 1 }}</span>\n</ng-template>\n\n<ng-template #customIndicator>\n <ng-content select=\"[igxStepIndicator]\"></ng-content>\n</ng-template>\n\n<div class=\"igx-stepper__step-header\" igxRipple [ngClass]=\"stepHeaderClasses\" (keydown)=\"handleKeydown($event)\"\n (click)=\"onPointerDown($event)\">\n\n @if (isIndicatorVisible) {\n <div class=\"igx-stepper__step-indicator\">\n <ng-container *ngTemplateOutlet=\"indicatorTemplate ? indicatorTemplate : defaultIndicator\"></ng-container>\n </div>\n }\n\n <div class=\"igx-stepper__step-title-wrapper\">\n <ng-container *ngTemplateOutlet=\"defaultTitle\"></ng-container>\n </div>\n</div>\n\n@if (!isHorizontal) {\n <div class=\"igx-stepper__step-content-wrapper\">\n <ng-container *ngTemplateOutlet=\"contentTemplate\"></ng-container>\n </div>\n}\n" }]
}], propDecorators: { id: [{
type: HostBinding,
args: ['attr.id']
}, {
type: Input
}], disabled: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], completed: [{
type: Input,
args: [{ transform: booleanAttribute }]
}, {
type: HostBinding,
args: ['class.igx-stepper__step--completed']
}], isValid: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], optional: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], active: [{
type: HostBinding,
args: ['attr.aria-selected']
}, {
type: Input,
args: [{ transform: booleanAttribute }]
}], tabIndex: [{
type: HostBinding,
args: ['attr.tabindex']
}, {
type: Input
}], role: [{
type: HostBinding,
args: ['attr.role']
}], contentId: [{
type: HostBinding,
args: ['attr.aria-controls']
}], cssClass: [{
type: HostBinding,
args: ['class.igx-stepper__step']
}], generalDisabled: [{
type: HostBinding,
args: ['class.igx-stepper__step--disabled']
}], titlePositionTop: [{
type: HostBinding,
args: ['class']
}], activeChange: [{
type: Output
}], contentTemplate: [{
type: ViewChild,
args: ['contentTemplate', { static: true }]
}], customIndicatorTemplate: [{
type: ViewChild,
args: ['customIndicator', { static: true }]
}], contentContainer: [{
type: ViewChild,
args: ['contentContainer']
}], indicator: [{
type: ContentChild,
args: [forwardRef(() => IgxStepIndicatorDirective)]
}], content: [{
type: ContentChild,
args: [forwardRef(() => IgxStepContentDirective)]
}], onFocus: [{
type: HostListener,
args: ['focus']
}], onBlur: [{
type: HostListener,
args: ['blur']
}], handleKeydown: [{
type: HostListener,
args: ['keydown', ['$event']]
}] } });
// TODO: common interface between IgxCarouselComponentBase and ToggleAnimationPlayer?
/**
* IgxStepper provides a wizard-like workflow by dividing content into logical steps.
*
* @igxModule IgxStepperModule
*
* @igxKeywords stepper
*
* @igxGroup Layouts
*
* @remarks
* The Ignite UI for Angular Stepper component allows the user to navigate between multiple steps.
* It supports horizontal and vertical orientation as well as keyboard navigation and provides API methods to control the active step.
* The component offers keyboard navigation and API to control the active step.
*
* @example
* ```html
* <igx-stepper>
* <igx-step [active]="true">
* <igx-icon igxStepIndicator>home</igx-icon>
* <p igxStepTitle>Home</p>
* <div igxStepContent>
* ...
* </div>
* </igx-step>
* <igx-step [optional]="true">
* <div igxStepContent>
* ...
* </div>
* </igx-step>
* <igx-step>
* <div igxStepContent>
* ...
* </div>
* </igx-step>
* </igx-stepper>
* ```
*/
class IgxStepperComponent extends IgxCarouselComponentBase {
/**
* Get/Set the animation type of the stepper when the orientation direction is vertical.
*
* @remarks
* Default value is `grow`. Other possible values are `fade` and `none`.
*
* ```html
* <igx-stepper verticalAnimationType="none">
* <igx-stepper>
* ```
*/
get verticalAnimationType() {
return this._verticalAnimationType;
}
set verticalAnimationType(value) {
// TODO: activeChange event is not emitted for the collapsing steps (loop through collapsing steps and emit)
this.stepperService.collapsingSteps.clear();
this._verticalAnimationType = value;
switch (value) {
case 'grow':
this.verticalAnimationSettings = this.updateVerticalAnimationSettings(growVerIn, growVerOut);
break;
case 'fade':
this.verticalAnimationSettings = this.updateVerticalAnimationSettings(fadeIn, null);
break;
case 'none':
this.verticalAnimationSettings = this.updateVerticalAnimationSettings(null, null);
break;
}
}
/**
* Get/Set the animation type of the stepper when the orientation direction is horizontal.
*
* @remarks
* Default value is `grow`. Other possible values are `fade` and `none`.
*
* ```html
* <igx-stepper animationType="none">
* <igx-stepper>
* ```
*/
get horizontalAnimationType() {
return this.animationType;
}
set horizontalAnimationType(value) {
// TODO: activeChange event is not emitted for the collapsing steps (loop through collapsing steps and emit)
this.stepperService.collapsingSteps.clear();
this.animationType = value;
}
/**
* Get/Set the animation duration.
* ```html
* <igx-stepper [animationDuration]="500">
* <igx-stepper>
* ```
*/
get animationDuration() {
return this.defaultAnimationDuration;
}
set animationDuration(value) {
if (value && value > 0) {
this.defaultAnimationDuration = value;
return;
}
this.defaultAnimationDuration = this._defaultAnimationDuration;
}
/**
* Get/Set whether the stepper is linear.
*
* @remarks
* If the stepper is in linear mode and if the active step is valid only then the user is able to move forward.
*
* ```html
* <igx-stepper [linear]="true"></igx-stepper>
* ```
*/
get linear() {
return this._linear;
}
set linear(value) {
this._linear = value;
if (this._linear && this.steps.length > 0) {
// when the stepper is in linear mode we should calculate which steps should be disabled
// and which are visited i.e. their validity should be correctly displayed.
this.stepperService.calculateVisitedSteps();
this.stepperService.calculateLinearDisabledSteps();
}
else {
this.stepperService.linearDisabledSteps.clear();
}
}
/**
* Get/Set the stepper orientation.
*
* ```typescript
* this.stepper.orientation = IgxStepperOrientation.Vertical;
* ```
*/
get orientation() {
return this._orientation;
}
set orientation(value) {
if (this._orientation === value) {
return;
}
// TODO: activeChange event is not emitted for the collapsing steps
this.stepperService.collapsingSteps.clear();
this._orientation = value;
this._defaultTitlePosition = this._orientation === IgxStepperOrientation.Horizontal ?
IgxStepperTitlePosition.Bottom : IgxStepperTitlePosition.End;
}
/** @hidden @internal **/
get directionClass() {
return this.orientation === IgxStepperOrientation.Horizontal;
}
/**
* Get all steps.
*
* ```typescript
* const steps: IgxStepComponent[] = this.stepper.steps;
* ```
*/
get steps() {
return this._steps?.toArray() || [];
}
/** @hidden @internal */
get nativeElement() {
return this.element.nativeElement;
}
constructor() {
super();
this.stepperService = inject(IgxStepperService);
this.element = inject(ElementRef);
/**
* Get/Set the type of the steps.
*
* ```typescript
* this.stepper.stepType = IgxStepType.Indicator;
* ```
*/
this.stepType = IgxStepType.Full;
/**
* Get/Set whether the content is displayed above the steps.
*
* @remarks
* Default value is `false` and the content is below the steps.
*
* ```typescript
* this.stepper.contentTop = true;
* ```
*/
this.contentTop = false;
/**
* Get/Set the position of the steps title.
*
* @remarks
* The default value when the stepper is horizontally orientated is `bottom`.
* In vertical layout the default title position is `end`.
*
* ```typescript
* this.stepper.titlePosition = IgxStepperTitlePosition.Top;
* ```
*/
this.titlePosition = null;
/** @hidden @internal **/
this.cssClass = 'igx-stepper';
/** @hidden @internal **/
this.role = 'tablist';
/**
* Emitted when the stepper's active step is changing.
*
*```html
* <igx-stepper (activeStepChanging)="handleActiveStepChanging($event)">
* </igx-stepper>
* ```
*
*```typescript
* public handleActiveStepChanging(event: IStepChangingEventArgs) {
* if (event.newIndex < event.oldIndex) {
* event.cancel = true;
* }
* }
*```
*/
this.activeStepChanging = new EventEmitter();
/**
* Emitted when the active step is changed.
*
* @example
* ```
* <igx-stepper (activeStepChanged)="handleActiveStepChanged($event)"></igx-stepper>
* ```
*/
this.activeStepChanged = new EventEmitter();
/** @hidden @internal */
this.verticalAnimationSettings = {
openAnimation: growVerIn,
closeAnimation: growVerOut,
};
/** @hidden @internal */
this._defaultTitlePosition = IgxStepperTitlePosition.Bottom;
this.destroy$ = new Subject();
this._orientation = IgxStepperOrientation.Horizontal;
this._verticalAnimationType = VerticalAnimationType.Grow;
this._linear = false;
this._defaultAnimationDuration = 350;
this.stepperService.stepper = this;
}
/** @hidden @internal */
ngOnChanges(changes) {
if (changes['animationDuration']) {
this.verticalAnimationType = this._verticalAnimationType;
}
}
/** @hidden @internal */
ngOnInit() {
this.enterAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.activeStepChanged.emit({ owner: this, index: this.stepperService.activeStep.index });
});
this.leaveAnimationDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
if (this.stepperService.collapsingSteps.size === 1) {
this.stepperService.collapse(this.stepperService.previousActiveStep);
}
else {
Array.from(this.stepperService.collapsingSteps).slice(0, this.stepperService.collapsingSteps.size - 1)
.forEach(step => this.stepperService.collapse(step));
}
});
}
/** @hidden @internal */
ngAfterContentInit() {
let activeStep;
this.steps.forEach((step, index) => {
this.updateStepAria(step, index);
if (!activeStep && step.active) {
activeStep = step;
}
});
if (!activeStep) {
this.activateFirstStep(true);
}
if (this.linear) {
this.stepperService.calculateLinearDisabledSteps();
}
this.handleStepChanges();
}
/** @hidden @internal */
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
/**
* Activates the step at a given index.
*
*```typescript
* this.stepper.navigateTo(1);
*```
*/
navigateTo(index) {
const step = this.steps[index];
if (!step || this.stepperService.activeStep === step) {
return;
}
this.activateStep(step);
}
/**
* Activates the next enabled step.
*
*```typescript
* this.stepper.next();
*```
*/
next() {
this.moveToNextStep();
}
/**
* Activates the previous enabled step.
*
*```typescript
* this.stepper.prev();
*```
*/
prev() {
this.moveToNextStep(false);
}
/**
* Resets the stepper to its initial state i.e. activates the first step.
*
* @remarks
* The steps' content will not be automatically reset.
*```typescript
* this.stepper.reset();
*```
*/
reset() {
this.stepperService.visitedSteps.clear();
const activeStep = this.steps.find(s => !s.disabled);
if (activeStep) {
this.activateStep(activeStep);
}
}
/** @hidden @internal */
playHorizontalAnimations() {
this.previousItem = this.stepperService.previousActiveStep;
this.currentItem = this.stepperService.activeStep;
this.triggerAnimations();
}
getPreviousElement() {
return this.stepperService.previousActiveStep?.contentContainer.nativeElement;
}
getCurrentElement() {
return this.stepperService.activeStep.contentContainer.nativeElement;
}
updateVerticalAnimationSettings(openAnimation, closeAnimation) {
const customCloseAnimation = useAnimation(closeAnimation, {
params: {
duration: this.animationDuration + 'ms'
}
});
const customOpenAnimation = useAnimation(openAnimation, {
params: {
duration: this.animationDuration + 'ms'
}
});
return {
openAnimation: openAnimation ? customOpenAnimation : null,
closeAnimation: closeAnimation ? customCloseAnimation : null
};
}
updateStepAria(step, index) {
step._index = index;
step.renderer.setAttribute(step.nativeElement, 'aria-se