/** * @license * Copyright Akveo. All Rights Reserved. * Licensed under the MIT License. See License.txt in the project root for license information. */ import { Component, ContentChildren, HostBinding, Input, QueryList, TemplateRef, } from '@angular/core'; import { convertToBoolProperty, NbBooleanInput } from '../helpers'; import { NB_STEPPER } from './stepper-tokens'; import { NbStepComponent } from './step.component'; export type NbStepperOrientation = 'vertical' | 'horizontal'; /** * Stepper component * * @stacked-example(Showcase, stepper/stepper-showcase.component) * * ### Installation * * Import `NbStepperModule` to your feature module. * ```ts * @NgModule({ * imports: [ * // ... * NbStepperModule, * ], * }) * export class PageModule { } * ``` * ### Usage * * If step label is string you can pass it as `label` attribute. Otherwise ng-template should be used: * ```html * // ... * * * // ... step content here * * * *
* step number two *
*
* // ... step content here *
*
* ``` * * When linear mode enabled user can't move forward unless current step is complete. * @stacked-example(Linear, stepper/stepper-linear.component) * * Specify `[stepControl]="form"` and stepper allow go to the next step only if form is valid. * You can disable it via `linear` mode setting. * ```html * // ... * * *
* // ... *
*
* // ... *
* ``` * * @stacked-example(Validation, stepper/stepper-validation.component) * * Stepper component has two layout options - `vertical` & `horizontal` * @stacked-example(Vertical, stepper/stepper-vertical.component) * * `disableStepNavigation` disables navigation by clicking on steps, so user can navigate only using * 'nbStepperPrevious' and 'nbStepperNext' buttons. * @stacked-example(Disabled steps navigation, stepper/stepper-disabled-step-nav.component) * * @styles * * stepper-step-text-color: * stepper-step-text-font-family: * stepper-step-text-font-size: * stepper-step-text-font-weight: * stepper-step-text-line-height: * stepper-step-active-text-color: * stepper-step-completed-text-color: * stepper-step-index-border-color: * stepper-step-index-border-style: * stepper-step-index-border-width: * stepper-step-index-border-radius: * stepper-step-index-width: * stepper-step-index-active-border-color: * stepper-step-index-completed-background-color: * stepper-step-index-completed-border-color: * stepper-step-index-completed-text-color: * stepper-connector-background-color: * stepper-connector-completed-background-color: * stepper-horizontal-connector-margin: * stepper-vertical-connector-margin: * stepper-step-content-padding: */ @Component({ selector: 'nb-stepper', styleUrls: ['./stepper.component.scss'], templateUrl: './stepper.component.html', providers: [{ provide: NB_STEPPER, useExisting: NbStepperComponent }], }) export class NbStepperComponent { /** * Selected step index */ @Input() get selectedIndex() { return this._selectedIndex; } set selectedIndex(index: number) { if (!this.steps) { this._selectedIndex = index; return; } this.markCurrentStepInteracted(); if (this.canBeSelected(index)) { this._selectedIndex = index; } } protected _selectedIndex: number = 0; /** * Disables navigation by clicking on steps. False by default * @param {boolean} value */ @Input() set disableStepNavigation(value: boolean) { this._disableStepNavigation = convertToBoolProperty(value); } get disableStepNavigation(): boolean { return this._disableStepNavigation; } protected _disableStepNavigation: boolean = false; static ngAcceptInputType_disableStepNavigation: NbBooleanInput; /** * Selected step component */ @Input() get selected(): NbStepComponent { return this.steps ? this.steps.toArray()[this.selectedIndex] : undefined; } set selected(step: NbStepComponent) { if (!this.steps) { return; } this.selectedIndex = this.steps.toArray().indexOf(step); } /** * Stepper orientation - `horizontal`|`vertical` */ @Input() orientation: NbStepperOrientation = 'horizontal'; /** * Allow moving forward only if the current step is complete * @default true */ @Input() set linear(value: boolean) { this._linear = convertToBoolProperty(value); } get linear(): boolean { return this._linear; } protected _linear = true; static ngAcceptInputType_linear: NbBooleanInput; @HostBinding('class.vertical') get vertical() { return this.orientation === 'vertical'; } @HostBinding('class.horizontal') get horizontal() { return this.orientation === 'horizontal'; } @ContentChildren(NbStepComponent) steps: QueryList; /** * Navigate to next step * */ next() { this.selectedIndex = Math.min(this.selectedIndex + 1, this.steps.length - 1); } /** * Navigate to previous step * */ previous() { this.selectedIndex = Math.max(this.selectedIndex - 1, 0); } /** * Reset stepper and stepControls to initial state * */ reset() { this._selectedIndex = 0; this.steps.forEach(step => step.reset()); } isStepSelected(step: NbStepComponent) { return this.selected === step; } /* * @docs-private **/ getStepTemplate(step: NbStepComponent): TemplateRef | null { if (step.isLabelTemplate) { return step.label as TemplateRef; } return null; } protected isStepValid(index: number): boolean { return this.steps.toArray()[index].completed; } protected canBeSelected(indexToCheck: number): boolean { const noSteps = !this.steps || this.steps.length === 0; if (noSteps || indexToCheck < 0 || indexToCheck >= this.steps.length) { return false; } if (indexToCheck <= this.selectedIndex || !this.linear) { return true; } let isAllStepsValid = true; for (let i = this.selectedIndex; i < indexToCheck; i++) { if (!this.isStepValid(i)) { isAllStepsValid = false; break; } } return isAllStepsValid; } protected markCurrentStepInteracted() { if (this.selected) { this.selected.interacted = true; } } }