mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(stepper): Create MAT_STEPPER_GLOBAL_OPTIONS InjectionToken (#11457)
This commit is contained in:
committed by
Jeremy Elbourn
parent
9d3d95f9e2
commit
9ab2c905e2
+105
-7
@@ -31,6 +31,7 @@ import {
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
InjectionToken,
|
||||
} from '@angular/core';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {AbstractControl} from '@angular/forms';
|
||||
@@ -65,6 +66,37 @@ export class StepperSelectionEvent {
|
||||
previouslySelectedStep: CdkStep;
|
||||
}
|
||||
|
||||
/** The state of each step. */
|
||||
export type StepState = 'number' | 'edit' | 'done' | 'error' | string;
|
||||
|
||||
/** Enum to represent the different states of the steps. */
|
||||
export const STEP_STATE = {
|
||||
NUMBER: 'number',
|
||||
EDIT: 'edit',
|
||||
DONE: 'done',
|
||||
ERROR: 'error'
|
||||
};
|
||||
|
||||
/** InjectionToken that can be used to specify the global stepper options. */
|
||||
export const MAT_STEPPER_GLOBAL_OPTIONS =
|
||||
new InjectionToken<StepperOptions>('mat-stepper-global-options');
|
||||
|
||||
/** Configurable options for stepper. */
|
||||
export interface StepperOptions {
|
||||
/**
|
||||
* Whether the stepper should display an error state or not.
|
||||
* Default behavior is assumed to be false.
|
||||
*/
|
||||
showError?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the stepper should display the default indicator type
|
||||
* or not.
|
||||
* Default behavior is assumed to be true.
|
||||
*/
|
||||
displayDefaultIndicatorType?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'cdk-step',
|
||||
@@ -74,6 +106,10 @@ export class StepperSelectionEvent {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CdkStep implements OnChanges {
|
||||
private _stepperOptions: StepperOptions;
|
||||
_showError: boolean;
|
||||
_displayDefaultIndicatorType: boolean;
|
||||
|
||||
/** Template for step label if it exists. */
|
||||
@ContentChild(CdkStepLabel) stepLabel: CdkStepLabel;
|
||||
|
||||
@@ -89,6 +125,9 @@ export class CdkStep implements OnChanges {
|
||||
/** Plain text label of the step. */
|
||||
@Input() label: string;
|
||||
|
||||
/** Error message to display when there's an error. */
|
||||
@Input() errorMessage: string;
|
||||
|
||||
/** Aria label for the tab. */
|
||||
@Input('aria-label') ariaLabel: string;
|
||||
|
||||
@@ -98,6 +137,9 @@ export class CdkStep implements OnChanges {
|
||||
*/
|
||||
@Input('aria-labelledby') ariaLabelledby: string;
|
||||
|
||||
/** State of the step. */
|
||||
@Input() state: StepState;
|
||||
|
||||
/** Whether the user can return to this step once it has been marked as complted. */
|
||||
@Input()
|
||||
get editable(): boolean { return this._editable; }
|
||||
@@ -117,18 +159,39 @@ export class CdkStep implements OnChanges {
|
||||
/** Whether step is marked as completed. */
|
||||
@Input()
|
||||
get completed(): boolean {
|
||||
return this._customCompleted == null ? this._defaultCompleted() : this._customCompleted;
|
||||
return this._customCompleted == null ? this._getDefaultCompleted() : this._customCompleted;
|
||||
}
|
||||
set completed(value: boolean) {
|
||||
this._customCompleted = coerceBooleanProperty(value);
|
||||
}
|
||||
private _customCompleted: boolean | null = null;
|
||||
|
||||
private _defaultCompleted() {
|
||||
private _getDefaultCompleted() {
|
||||
return this.stepControl ? this.stepControl.valid && this.interacted : this.interacted;
|
||||
}
|
||||
|
||||
constructor(@Inject(forwardRef(() => CdkStepper)) private _stepper: CdkStepper) { }
|
||||
/** Whether step has an error. */
|
||||
@Input()
|
||||
get hasError(): boolean {
|
||||
return this._customError || this._getDefaultError();
|
||||
}
|
||||
set hasError(value: boolean) {
|
||||
this._customError = coerceBooleanProperty(value);
|
||||
}
|
||||
private _customError: boolean | null = null;
|
||||
|
||||
private _getDefaultError() {
|
||||
return this.stepControl && this.stepControl.invalid && this.interacted;
|
||||
}
|
||||
|
||||
/** @breaking-change 8.0.0 remove the `?` after `stepperOptions` */
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CdkStepper)) private _stepper: CdkStepper,
|
||||
@Optional() @Inject(MAT_STEPPER_GLOBAL_OPTIONS) stepperOptions?: StepperOptions) {
|
||||
this._stepperOptions = stepperOptions ? stepperOptions : {};
|
||||
this._displayDefaultIndicatorType = this._stepperOptions.displayDefaultIndicatorType !== false;
|
||||
this._showError = !!this._stepperOptions.showError;
|
||||
}
|
||||
|
||||
/** Selects this step component. */
|
||||
select(): void {
|
||||
@@ -143,6 +206,10 @@ export class CdkStep implements OnChanges {
|
||||
this._customCompleted = false;
|
||||
}
|
||||
|
||||
if (this._customError != null) {
|
||||
this._customError = false;
|
||||
}
|
||||
|
||||
if (this.stepControl) {
|
||||
this.stepControl.reset();
|
||||
}
|
||||
@@ -301,15 +368,46 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
/** Returns the type of icon to be displayed. */
|
||||
_getIndicatorType(index: number): 'number' | 'edit' | 'done' {
|
||||
_getIndicatorType(index: number, state: StepState = STEP_STATE.NUMBER): StepState {
|
||||
const step = this._steps.toArray()[index];
|
||||
if (!step.completed || this._selectedIndex == index) {
|
||||
return 'number';
|
||||
const isCurrentStep = this._isCurrentStep(index);
|
||||
|
||||
return step._displayDefaultIndicatorType
|
||||
? this._getDefaultIndicatorLogic(step, isCurrentStep)
|
||||
: this._getGuidelineLogic(step, isCurrentStep, state);
|
||||
}
|
||||
|
||||
private _getDefaultIndicatorLogic(step: CdkStep, isCurrentStep: boolean): StepState {
|
||||
if (step._showError && step.hasError && !isCurrentStep) {
|
||||
return STEP_STATE.ERROR;
|
||||
} else if (!step.completed || isCurrentStep) {
|
||||
return STEP_STATE.NUMBER;
|
||||
} else {
|
||||
return step.editable ? 'edit' : 'done';
|
||||
return step.editable ? STEP_STATE.EDIT : STEP_STATE.DONE;
|
||||
}
|
||||
}
|
||||
|
||||
private _getGuidelineLogic(
|
||||
step: CdkStep,
|
||||
isCurrentStep: boolean,
|
||||
state: StepState = STEP_STATE.NUMBER): StepState {
|
||||
if (step._showError && step.hasError && !isCurrentStep) {
|
||||
return STEP_STATE.ERROR;
|
||||
} else if (step.completed && !isCurrentStep) {
|
||||
return STEP_STATE.DONE;
|
||||
} else if (step.completed && isCurrentStep) {
|
||||
return state;
|
||||
} else if (step.editable && isCurrentStep) {
|
||||
return STEP_STATE.EDIT;
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
private _isCurrentStep(index: number) {
|
||||
return this._selectedIndex === index;
|
||||
}
|
||||
|
||||
/** Returns the index of the currently-focused step header. */
|
||||
_getFocusIndex() {
|
||||
return this._keyManager ? this._keyManager.activeItemIndex : this._selectedIndex;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
$primary: map-get($theme, primary);
|
||||
$warn: map-get($theme, warn);
|
||||
|
||||
.mat-step-header {
|
||||
&.cdk-keyboard-focused,
|
||||
@@ -20,18 +21,29 @@
|
||||
}
|
||||
|
||||
.mat-step-icon {
|
||||
background-color: mat-color($primary);
|
||||
color: mat-color($primary, default-contrast);
|
||||
}
|
||||
|
||||
.mat-step-icon-not-touched {
|
||||
background-color: mat-color($foreground, disabled-text);
|
||||
color: mat-color($primary, default-contrast);
|
||||
}
|
||||
|
||||
.mat-step-icon-selected,
|
||||
.mat-step-icon-state-done,
|
||||
.mat-step-icon-state-edit {
|
||||
background-color: mat-color($primary);
|
||||
color: mat-color($primary, default-contrast);
|
||||
}
|
||||
|
||||
.mat-step-icon-state-error {
|
||||
background-color: transparent;
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
.mat-step-label.mat-step-label-active {
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
|
||||
.mat-step-label.mat-step-label-error {
|
||||
color: mat-color($warn);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-stepper-horizontal, .mat-stepper-vertical {
|
||||
@@ -59,6 +71,14 @@
|
||||
};
|
||||
}
|
||||
|
||||
.mat-step-sub-label-error {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.mat-step-label-error {
|
||||
font-size: mat-font-size($config, body-2);
|
||||
}
|
||||
|
||||
.mat-step-label-selected {
|
||||
font: {
|
||||
size: mat-font-size($config, body-2);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<div class="mat-step-header-ripple" mat-ripple [matRippleTrigger]="_getHostElement()"></div>
|
||||
<div [class.mat-step-icon]="state !== 'number' || selected"
|
||||
[class.mat-step-icon-not-touched]="state == 'number' && !selected"
|
||||
<div class="mat-step-icon-state-{{state}} mat-step-icon" [class.mat-step-icon-selected]="selected"
|
||||
[ngSwitch]="state">
|
||||
|
||||
<ng-container *ngSwitchCase="'number'" [ngSwitch]="!!(iconOverrides && iconOverrides.number)">
|
||||
<ng-container
|
||||
*ngSwitchCase="true"
|
||||
@@ -26,16 +24,35 @@
|
||||
[ngTemplateOutletContext]="_getIconContext()"></ng-container>
|
||||
<mat-icon class="mat-step-icon-content" *ngSwitchDefault>done</mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'error'" [ngSwitch]="!!(iconOverrides && iconOverrides.error)">
|
||||
<ng-container
|
||||
*ngSwitchCase="true"
|
||||
[ngTemplateOutlet]="iconOverrides.error"
|
||||
[ngTemplateOutletContext]="_getIconContext()"></ng-container>
|
||||
<mat-icon *ngSwitchDefault>warning</mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<!-- Custom state. -->
|
||||
<ng-container *ngSwitchDefault [ngSwitch]="!!(iconOverrides && iconOverrides[state])">
|
||||
<ng-container
|
||||
*ngSwitchCase="true"
|
||||
[ngTemplateOutlet]="iconOverrides[state]"
|
||||
[ngTemplateOutletContext]="_getIconContext()"></ng-container>
|
||||
<mat-icon *ngSwitchDefault>{{state}}</mat-icon>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="mat-step-label"
|
||||
[class.mat-step-label-active]="active"
|
||||
[class.mat-step-label-selected]="selected">
|
||||
[class.mat-step-label-selected]="selected"
|
||||
[class.mat-step-label-error]="state == 'error'">
|
||||
<!-- If there is a label template, use it. -->
|
||||
<ng-container *ngIf="_templateLabel()" [ngTemplateOutlet]="_templateLabel()!.template">
|
||||
</ng-container>
|
||||
<!-- It there is no label template, fall back to the text label. -->
|
||||
<!-- If there is no label template, fall back to the text label. -->
|
||||
<div class="mat-step-text-label" *ngIf="_stringLabel()">{{label}}</div>
|
||||
|
||||
<div class="mat-step-optional" *ngIf="optional">{{_intl.optionalLabel}}</div>
|
||||
<div class="mat-step-optional" *ngIf="optional && state != 'error'">{{_intl.optionalLabel}}</div>
|
||||
<div class="mat-step-sub-label-error" *ngIf="state == 'error'">{{errorMessage}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ $mat-stepper-label-min-width: 50px !default;
|
||||
$mat-stepper-side-gap: 24px !default;
|
||||
$mat-vertical-stepper-content-margin: 36px !default;
|
||||
$mat-stepper-line-gap: 8px !default;
|
||||
$mat-step-optional-font-size: 12px;
|
||||
$mat-step-sub-label-font-size: 12px;
|
||||
$mat-step-header-icon-size: 16px !default;
|
||||
|
||||
.mat-step-header {
|
||||
@@ -17,12 +17,12 @@ $mat-step-header-icon-size: 16px !default;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.mat-step-optional {
|
||||
font-size: $mat-step-optional-font-size;
|
||||
.mat-step-optional,
|
||||
.mat-step-sub-label-error {
|
||||
font-size: $mat-step-sub-label-font-size;
|
||||
}
|
||||
|
||||
.mat-step-icon,
|
||||
.mat-step-icon-not-touched {
|
||||
.mat-step-icon {
|
||||
border-radius: 50%;
|
||||
height: $mat-stepper-label-header-height;
|
||||
width: $mat-stepper-label-header-height;
|
||||
@@ -44,6 +44,12 @@ $mat-step-header-icon-size: 16px !default;
|
||||
width: $mat-step-header-icon-size;
|
||||
}
|
||||
|
||||
.mat-step-icon-state-error .mat-icon {
|
||||
font-size: $mat-step-header-icon-size + 8;
|
||||
height: $mat-step-header-icon-size + 8;
|
||||
width: $mat-step-header-icon-size + 8;
|
||||
}
|
||||
|
||||
.mat-step-label {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -21,7 +21,7 @@ import {Subscription} from 'rxjs';
|
||||
import {MatStepLabel} from './step-label';
|
||||
import {MatStepperIntl} from './stepper-intl';
|
||||
import {MatStepperIconContext} from './stepper-icon';
|
||||
|
||||
import {StepState} from '@angular/cdk/stepper';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -39,11 +39,14 @@ export class MatStepHeader implements OnDestroy {
|
||||
private _intlSubscription: Subscription;
|
||||
|
||||
/** State of the given step. */
|
||||
@Input() state: string;
|
||||
@Input() state: StepState;
|
||||
|
||||
/** Label of the given step. */
|
||||
@Input() label: MatStepLabel | string;
|
||||
|
||||
/** Error message to display when there's an error. */
|
||||
@Input() errorMessage: string;
|
||||
|
||||
/** Overrides for the header icons, passed in via the stepper. */
|
||||
@Input() iconOverrides: {[key: string]: TemplateRef<MatStepperIconContext>};
|
||||
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
[attr.aria-label]="step.ariaLabel || null"
|
||||
[attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null"
|
||||
[index]="i"
|
||||
[state]="_getIndicatorType(i)"
|
||||
[state]="_getIndicatorType(i, step.state)"
|
||||
[label]="step.stepLabel || step.label"
|
||||
[selected]="selectedIndex === i"
|
||||
[active]="step.completed || selectedIndex === i || !linear"
|
||||
[optional]="step.optional"
|
||||
[errorMessage]="step.errorMessage"
|
||||
[iconOverrides]="_iconOverrides">
|
||||
</mat-step-header>
|
||||
<div *ngIf="!isLast" class="mat-stepper-horizontal-line"></div>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Directive, Input, TemplateRef} from '@angular/core';
|
||||
import {StepState} from '@angular/cdk/stepper';
|
||||
|
||||
/** Template context available to an attached `matStepperIcon`. */
|
||||
export interface MatStepperIconContext {
|
||||
@@ -26,7 +27,7 @@ export interface MatStepperIconContext {
|
||||
})
|
||||
export class MatStepperIcon {
|
||||
/** Name of the icon to be overridden. */
|
||||
@Input('matStepperIcon') name: 'edit' | 'done' | 'number';
|
||||
@Input('matStepperIcon') name: StepState;
|
||||
|
||||
constructor(public templateRef: TemplateRef<MatStepperIconContext>) {}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
[attr.aria-label]="step.ariaLabel || null"
|
||||
[attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null"
|
||||
[index]="i"
|
||||
[state]="_getIndicatorType(i)"
|
||||
[state]="_getIndicatorType(i, step.state)"
|
||||
[label]="step.stepLabel || step.label"
|
||||
[selected]="selectedIndex === i"
|
||||
[active]="step.completed || selectedIndex === i || !linear"
|
||||
[optional]="step.optional"
|
||||
[errorMessage]="step.errorMessage"
|
||||
[iconOverrides]="_iconOverrides">
|
||||
</mat-step-header>
|
||||
|
||||
|
||||
@@ -156,6 +156,71 @@ by placing a `matStepperIcon` for each of the icons that you want to override. T
|
||||
|
||||
Note that you aren't limited to using the `mat-icon` component when providing custom icons.
|
||||
|
||||
#### Step States
|
||||
You can set the state of a step to whatever you want. The given state by default maps to an icon. However, it can be overridden the same way as mentioned above.
|
||||
|
||||
```html
|
||||
<mat-horizontal-stepper>
|
||||
<mat-step label="Step 1" state="phone">
|
||||
<p>Put down your phones.</p>
|
||||
<div>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<mat-step label="Step 2" state="chat">
|
||||
<p>Socialize with each other.</p>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<mat-step label="Step 3">
|
||||
<p>You're welcome.</p>
|
||||
</mat-step>
|
||||
|
||||
<!-- Icon overrides. -->
|
||||
<ng-template matStepperIcon="phone">
|
||||
<mat-icon>call_end</mat-icon>
|
||||
</ng-template>
|
||||
<ng-template matStepperIcon="chat">
|
||||
<mat-icon>forum</mat-icon>
|
||||
</ng-template>
|
||||
</mat-horizontal-stepper>
|
||||
```
|
||||
|
||||
In order to use the custom step states, you must add the `displayDefaultIndicatorType` option to the global default stepper options which can be specified by providing a value for
|
||||
`MAT_STEPPER_GLOBAL_OPTIONS` in your application's root module.
|
||||
|
||||
```ts
|
||||
@NgModule({
|
||||
providers: [
|
||||
{
|
||||
provide: MAT_STEPPER_GLOBAL_OPTIONS,
|
||||
useValue: { displayDefaultIndicatorType: false }
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
<!-- example(stepper-states) -->
|
||||
|
||||
### Error State
|
||||
|
||||
The stepper can now show error states by simply providing the `showError` option to the `MAT_STEPPER_GLOBAL_OPTIONS` in your application's root module as mentioned above.
|
||||
|
||||
```ts
|
||||
@NgModule({
|
||||
providers: [
|
||||
{
|
||||
provide: MAT_STEPPER_GLOBAL_OPTIONS,
|
||||
useValue: { showError: true }
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
<!-- example(stepper-errors) -->
|
||||
|
||||
### Keyboard interaction
|
||||
- <kbd>LEFT_ARROW</kbd>: Focuses the previous step header
|
||||
- <kbd>RIGHT_ARROW</kbd>: Focuses the next step header
|
||||
|
||||
@@ -34,8 +34,7 @@ $mat-stepper-line-gap: 8px !default;
|
||||
align-items: center;
|
||||
padding: 0 $mat-stepper-side-gap;
|
||||
|
||||
.mat-step-icon,
|
||||
.mat-step-icon-not-touched {
|
||||
.mat-step-icon {
|
||||
margin-right: $mat-stepper-line-gap;
|
||||
flex: none;
|
||||
|
||||
@@ -52,8 +51,7 @@ $mat-stepper-line-gap: 8px !default;
|
||||
padding: $mat-stepper-side-gap;
|
||||
max-height: $mat-stepper-label-header-height;
|
||||
|
||||
.mat-step-icon,
|
||||
.mat-step-icon-not-touched {
|
||||
.mat-step-icon {
|
||||
margin-right: $mat-vertical-stepper-content-margin - $mat-stepper-side-gap;
|
||||
|
||||
[dir='rtl'] & {
|
||||
|
||||
+262
-141
@@ -9,10 +9,10 @@ import {
|
||||
SPACE,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {StepperOrientation} from '@angular/cdk/stepper';
|
||||
import {StepperOrientation, MAT_STEPPER_GLOBAL_OPTIONS, STEP_STATE} from '@angular/cdk/stepper';
|
||||
import {dispatchKeyboardEvent} from '@angular/cdk/testing';
|
||||
import {Component, DebugElement, EventEmitter, OnInit} from '@angular/core';
|
||||
import {async, ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing';
|
||||
import {Component, DebugElement, EventEmitter, OnInit, Type, Provider} from '@angular/core';
|
||||
import {ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing';
|
||||
import {
|
||||
AbstractControl,
|
||||
AsyncValidatorFn,
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
FormGroup,
|
||||
ReactiveFormsModule,
|
||||
ValidationErrors,
|
||||
Validators
|
||||
Validators,
|
||||
FormBuilder
|
||||
} from '@angular/forms';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
@@ -30,45 +31,26 @@ import {MatStepperModule} from './index';
|
||||
import {MatHorizontalStepper, MatStep, MatStepper, MatVerticalStepper} from './stepper';
|
||||
import {MatStepperNext, MatStepperPrevious} from './stepper-button';
|
||||
import {MatStepperIntl} from './stepper-intl';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {MatInputModule} from '../input/input-module';
|
||||
|
||||
|
||||
const VALID_REGEX = /valid/;
|
||||
let dir: {value: Direction, change: EventEmitter<Direction>};
|
||||
|
||||
describe('MatStepper', () => {
|
||||
let dir: {value: Direction, change: EventEmitter<Direction>};
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(() => {
|
||||
dir = {
|
||||
value: 'ltr',
|
||||
change: new EventEmitter()
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatStepperModule, NoopAnimationsModule, ReactiveFormsModule],
|
||||
declarations: [
|
||||
SimpleMatVerticalStepperApp,
|
||||
LinearMatVerticalStepperApp,
|
||||
IconOverridesStepper,
|
||||
SimplePreselectedMatHorizontalStepperApp,
|
||||
SimpleStepperWithoutStepControl,
|
||||
SimpleStepperWithStepControlAndCompletedBinding,
|
||||
SimpleMatHorizontalStepperApp,
|
||||
LinearStepperWithValidOptionalStep,
|
||||
StepperWithAriaInputs,
|
||||
],
|
||||
providers: [
|
||||
{provide: Directionality, useFactory: () => dir}
|
||||
]
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('basic stepper', () => {
|
||||
let fixture: ComponentFixture<SimpleMatVerticalStepperApp>;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
|
||||
fixture = createComponent(SimpleMatVerticalStepperApp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -316,69 +298,27 @@ describe('MatStepper', () => {
|
||||
expect(stepperComponent._getIndicatorType(0)).toBe('done');
|
||||
});
|
||||
|
||||
it('should re-render when the i18n labels change', inject([MatStepperIntl],
|
||||
(intl: MatStepperIntl) => {
|
||||
fixture.destroy();
|
||||
it('should emit an event when the enter animation is done', fakeAsync(() => {
|
||||
let stepper = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
|
||||
let selectionChangeSpy = jasmine.createSpy('selectionChange spy');
|
||||
let animationDoneSpy = jasmine.createSpy('animationDone spy');
|
||||
let selectionChangeSubscription = stepper.selectionChange.subscribe(selectionChangeSpy);
|
||||
let animationDoneSubscription = stepper.animationDone.subscribe(animationDoneSpy);
|
||||
|
||||
const i18nFixture = TestBed.createComponent(SimpleMatHorizontalStepperApp);
|
||||
i18nFixture.detectChanges();
|
||||
stepper.selectedIndex = 1;
|
||||
fixture.detectChanges();
|
||||
|
||||
const header =
|
||||
i18nFixture.debugElement.queryAll(By.css('mat-step-header'))[2].nativeElement;
|
||||
const optionalLabel = header.querySelector('.mat-step-optional');
|
||||
expect(selectionChangeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(animationDoneSpy).not.toHaveBeenCalled();
|
||||
|
||||
expect(optionalLabel).toBeTruthy();
|
||||
expect(optionalLabel.textContent).toBe('Optional');
|
||||
flush();
|
||||
|
||||
intl.optionalLabel = 'Valgfri';
|
||||
intl.changes.next();
|
||||
i18nFixture.detectChanges();
|
||||
expect(selectionChangeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(animationDoneSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(optionalLabel.textContent).toBe('Valgfri');
|
||||
}));
|
||||
|
||||
it('should emit an event when the enter animation is done', fakeAsync(() => {
|
||||
let stepper = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
|
||||
let selectionChangeSpy = jasmine.createSpy('selectionChange spy');
|
||||
let animationDoneSpy = jasmine.createSpy('animationDone spy');
|
||||
let selectionChangeSubscription = stepper.selectionChange.subscribe(selectionChangeSpy);
|
||||
let animationDoneSubscription = stepper.animationDone.subscribe(animationDoneSpy);
|
||||
|
||||
stepper.selectedIndex = 1;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(selectionChangeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(animationDoneSpy).not.toHaveBeenCalled();
|
||||
|
||||
flush();
|
||||
|
||||
expect(selectionChangeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(animationDoneSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
selectionChangeSubscription.unsubscribe();
|
||||
animationDoneSubscription.unsubscribe();
|
||||
}));
|
||||
|
||||
it('should not throw when attempting to get the selected step too early', () => {
|
||||
fixture.destroy();
|
||||
fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
|
||||
|
||||
const stepperComponent: MatVerticalStepper = fixture.debugElement
|
||||
.query(By.css('mat-vertical-stepper')).componentInstance;
|
||||
|
||||
expect(() => stepperComponent.selected).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw when attempting to set the selected step too early', () => {
|
||||
fixture.destroy();
|
||||
fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
|
||||
|
||||
const stepperComponent: MatVerticalStepper = fixture.debugElement
|
||||
.query(By.css('mat-vertical-stepper')).componentInstance;
|
||||
|
||||
expect(() => stepperComponent.selected = null!).not.toThrow();
|
||||
expect(stepperComponent.selectedIndex).toBe(-1);
|
||||
});
|
||||
selectionChangeSubscription.unsubscribe();
|
||||
animationDoneSubscription.unsubscribe();
|
||||
}));
|
||||
|
||||
it('should set the correct aria-posinset and aria-setsize', () => {
|
||||
const headers =
|
||||
@@ -405,14 +345,59 @@ describe('MatStepper', () => {
|
||||
|
||||
expect(stepperComponent.selectedIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('basic stepper when attempting to set the selected step too early', () => {
|
||||
it('should not throw', () => {
|
||||
const fixture = createComponent(SimpleMatVerticalStepperApp);
|
||||
const stepperComponent: MatVerticalStepper = fixture.debugElement
|
||||
.query(By.css('mat-vertical-stepper')).componentInstance;
|
||||
|
||||
expect(() => stepperComponent.selected).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('basic stepper when attempting to set the selected step too early', () => {
|
||||
it('should not throw', () => {
|
||||
const fixture = createComponent(SimpleMatVerticalStepperApp);
|
||||
const stepperComponent: MatVerticalStepper = fixture.debugElement
|
||||
.query(By.css('mat-vertical-stepper')).componentInstance;
|
||||
|
||||
expect(() => stepperComponent.selected = null!).not.toThrow();
|
||||
expect(stepperComponent.selectedIndex).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('basic stepper with i18n label change', () => {
|
||||
let i18nFixture;
|
||||
|
||||
beforeEach(() => {
|
||||
i18nFixture = createComponent(SimpleMatHorizontalStepperApp);
|
||||
i18nFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should re-render when the i18n labels change', inject([MatStepperIntl],
|
||||
(intl: MatStepperIntl) => {
|
||||
const header =
|
||||
i18nFixture.debugElement.queryAll(By.css('mat-step-header'))[2].nativeElement;
|
||||
const optionalLabel = header.querySelector('.mat-step-optional');
|
||||
|
||||
expect(optionalLabel).toBeTruthy();
|
||||
expect(optionalLabel.textContent).toBe('Optional');
|
||||
|
||||
intl.optionalLabel = 'Valgfri';
|
||||
intl.changes.next();
|
||||
i18nFixture.detectChanges();
|
||||
|
||||
expect(optionalLabel.textContent).toBe('Valgfri');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('icon overrides', () => {
|
||||
let fixture: ComponentFixture<IconOverridesStepper>;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IconOverridesStepper);
|
||||
fixture = createComponent(IconOverridesStepper);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -455,7 +440,7 @@ describe('MatStepper', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
dir.value = 'rtl';
|
||||
fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
|
||||
fixture = createComponent(SimpleMatVerticalStepperApp);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -488,7 +473,7 @@ describe('MatStepper', () => {
|
||||
let stepperComponent: MatVerticalStepper;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LinearMatVerticalStepperApp);
|
||||
fixture = createComponent(LinearMatVerticalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
testComponent = fixture.componentInstance;
|
||||
@@ -700,66 +685,71 @@ describe('MatStepper', () => {
|
||||
expect(steps[2].completed).toBe(true,
|
||||
'Expected third step to be considered complete when doing a run after a reset.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw when there is a pre-defined selectedIndex', () => {
|
||||
fixture.destroy();
|
||||
|
||||
let preselectedFixture = TestBed.createComponent(SimplePreselectedMatHorizontalStepperApp);
|
||||
expect(() => preselectedFixture.detectChanges()).not.toThrow();
|
||||
describe('linear stepper with a pre-defined selectedIndex', () => {
|
||||
let preselectedFixture;
|
||||
beforeEach(() => {
|
||||
preselectedFixture = createComponent(SimplePreselectedMatHorizontalStepperApp);
|
||||
});
|
||||
|
||||
it('should not move to the next step if the current one is not completed ' +
|
||||
'and there is no `stepControl`', () => {
|
||||
fixture.destroy();
|
||||
it('should not throw', () => {
|
||||
expect(() => preselectedFixture.detectChanges()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
const noStepControlFixture = TestBed.createComponent(SimpleStepperWithoutStepControl);
|
||||
describe('linear stepper with no `stepControl`', () => {
|
||||
let noStepControlFixture;
|
||||
beforeEach(() => {
|
||||
noStepControlFixture = createComponent(SimpleStepperWithoutStepControl);
|
||||
noStepControlFixture.detectChanges();
|
||||
});
|
||||
it('should not move to the next step if the current one is not completed ', () => {
|
||||
const stepper: MatHorizontalStepper = noStepControlFixture.debugElement
|
||||
.query(By.directive(MatHorizontalStepper)).componentInstance;
|
||||
|
||||
noStepControlFixture.detectChanges();
|
||||
const headers = noStepControlFixture.debugElement
|
||||
.queryAll(By.css('.mat-horizontal-stepper-header'));
|
||||
|
||||
const stepper: MatHorizontalStepper = noStepControlFixture.debugElement
|
||||
expect(stepper.selectedIndex).toBe(0);
|
||||
|
||||
headers[1].nativeElement.click();
|
||||
noStepControlFixture.detectChanges();
|
||||
|
||||
expect(stepper.selectedIndex).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('linear stepper with `stepControl`', () => {
|
||||
let controlAndBindingFixture;
|
||||
beforeEach(() => {
|
||||
controlAndBindingFixture =
|
||||
createComponent(SimpleStepperWithStepControlAndCompletedBinding);
|
||||
controlAndBindingFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should have the `stepControl` take precedence when `completed` is set', () => {
|
||||
expect(controlAndBindingFixture.componentInstance.steps[0].control.valid).toBe(true);
|
||||
expect(controlAndBindingFixture.componentInstance.steps[0].completed).toBe(false);
|
||||
|
||||
const stepper: MatHorizontalStepper = controlAndBindingFixture.debugElement
|
||||
.query(By.directive(MatHorizontalStepper)).componentInstance;
|
||||
|
||||
const headers = noStepControlFixture.debugElement
|
||||
const headers = controlAndBindingFixture.debugElement
|
||||
.queryAll(By.css('.mat-horizontal-stepper-header'));
|
||||
|
||||
expect(stepper.selectedIndex).toBe(0);
|
||||
|
||||
headers[1].nativeElement.click();
|
||||
noStepControlFixture.detectChanges();
|
||||
controlAndBindingFixture.detectChanges();
|
||||
|
||||
expect(stepper.selectedIndex).toBe(0);
|
||||
expect(stepper.selectedIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('should have the `stepControl` take precedence when both `completed` and ' +
|
||||
'`stepControl` are set', () => {
|
||||
fixture.destroy();
|
||||
|
||||
const controlAndBindingFixture =
|
||||
TestBed.createComponent(SimpleStepperWithStepControlAndCompletedBinding);
|
||||
|
||||
controlAndBindingFixture.detectChanges();
|
||||
|
||||
expect(controlAndBindingFixture.componentInstance.steps[0].control.valid).toBe(true);
|
||||
expect(controlAndBindingFixture.componentInstance.steps[0].completed).toBe(false);
|
||||
|
||||
const stepper: MatHorizontalStepper = controlAndBindingFixture.debugElement
|
||||
.query(By.directive(MatHorizontalStepper)).componentInstance;
|
||||
|
||||
const headers = controlAndBindingFixture.debugElement
|
||||
.queryAll(By.css('.mat-horizontal-stepper-header'));
|
||||
|
||||
expect(stepper.selectedIndex).toBe(0);
|
||||
|
||||
headers[1].nativeElement.click();
|
||||
controlAndBindingFixture.detectChanges();
|
||||
|
||||
expect(stepper.selectedIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vertical stepper', () => {
|
||||
it('should set the aria-orientation to "vertical"', () => {
|
||||
let fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
|
||||
let fixture = createComponent(SimpleMatVerticalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
let stepperEl = fixture.debugElement.query(By.css('mat-vertical-stepper')).nativeElement;
|
||||
@@ -767,7 +757,7 @@ describe('MatStepper', () => {
|
||||
});
|
||||
|
||||
it('should support using the left/right arrows to move focus', () => {
|
||||
let fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
|
||||
let fixture = createComponent(SimpleMatVerticalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
|
||||
@@ -775,7 +765,7 @@ describe('MatStepper', () => {
|
||||
});
|
||||
|
||||
it('should support using the up/down arrows to move focus', () => {
|
||||
let fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
|
||||
let fixture = createComponent(SimpleMatVerticalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
|
||||
@@ -784,7 +774,7 @@ describe('MatStepper', () => {
|
||||
|
||||
it('should reverse arrow key focus in RTL mode', () => {
|
||||
dir.value = 'rtl';
|
||||
let fixture = TestBed.createComponent(SimpleMatVerticalStepperApp);
|
||||
let fixture = createComponent(SimpleMatVerticalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
|
||||
@@ -794,7 +784,7 @@ describe('MatStepper', () => {
|
||||
|
||||
describe('horizontal stepper', () => {
|
||||
it('should set the aria-orientation to "horizontal"', () => {
|
||||
let fixture = TestBed.createComponent(SimpleMatHorizontalStepperApp);
|
||||
let fixture = createComponent(SimpleMatHorizontalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
let stepperEl = fixture.debugElement.query(By.css('mat-horizontal-stepper')).nativeElement;
|
||||
@@ -802,7 +792,7 @@ describe('MatStepper', () => {
|
||||
});
|
||||
|
||||
it('should support using the left/right arrows to move focus', () => {
|
||||
let fixture = TestBed.createComponent(SimpleMatHorizontalStepperApp);
|
||||
let fixture = createComponent(SimpleMatHorizontalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
|
||||
@@ -811,7 +801,7 @@ describe('MatStepper', () => {
|
||||
|
||||
it('should reverse arrow key focus in RTL mode', () => {
|
||||
dir.value = 'rtl';
|
||||
let fixture = TestBed.createComponent(SimpleMatHorizontalStepperApp);
|
||||
let fixture = createComponent(SimpleMatHorizontalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
|
||||
@@ -819,7 +809,7 @@ describe('MatStepper', () => {
|
||||
});
|
||||
|
||||
it('should reverse arrow key focus when switching into RTL after init', () => {
|
||||
let fixture = TestBed.createComponent(SimpleMatHorizontalStepperApp);
|
||||
let fixture = createComponent(SimpleMatHorizontalStepperApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
|
||||
@@ -833,13 +823,13 @@ describe('MatStepper', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('valid step in linear stepper', () => {
|
||||
describe('linear stepper with valid step', () => {
|
||||
let fixture: ComponentFixture<LinearStepperWithValidOptionalStep>;
|
||||
let testComponent: LinearStepperWithValidOptionalStep;
|
||||
let stepper: MatStepper;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LinearStepperWithValidOptionalStep);
|
||||
fixture = createComponent(LinearStepperWithValidOptionalStep);
|
||||
fixture.detectChanges();
|
||||
|
||||
testComponent = fixture.componentInstance;
|
||||
@@ -875,7 +865,7 @@ describe('MatStepper', () => {
|
||||
let stepHeader: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(StepperWithAriaInputs);
|
||||
fixture = createComponent(StepperWithAriaInputs);
|
||||
fixture.detectChanges();
|
||||
stepHeader = fixture.nativeElement.querySelector('.mat-step-header');
|
||||
});
|
||||
@@ -909,6 +899,76 @@ describe('MatStepper', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('stepper with error state', () => {
|
||||
let fixture: ComponentFixture<MatHorizontalStepperWithErrorsApp>;
|
||||
let stepper: MatStepper;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = createComponent(
|
||||
MatHorizontalStepperWithErrorsApp,
|
||||
[{
|
||||
provide: MAT_STEPPER_GLOBAL_OPTIONS,
|
||||
useValue: {showError: true}
|
||||
}],
|
||||
[MatFormFieldModule, MatInputModule]
|
||||
);
|
||||
fixture.detectChanges();
|
||||
stepper = fixture.debugElement
|
||||
.query(By.css('mat-horizontal-stepper')).componentInstance;
|
||||
});
|
||||
|
||||
it('should show error state', () => {
|
||||
let nextButtonNativeEl = fixture.debugElement
|
||||
.queryAll(By.directive(MatStepperNext))[0].nativeElement;
|
||||
|
||||
stepper.selectedIndex = 1;
|
||||
stepper._steps.first.hasError = true;
|
||||
nextButtonNativeEl.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.ERROR);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stepper using Material UI Guideline logic', () => {
|
||||
let fixture: ComponentFixture<MatHorizontalStepperWithErrorsApp>;
|
||||
let stepper: MatStepper;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = createComponent(
|
||||
MatHorizontalStepperWithErrorsApp,
|
||||
[{
|
||||
provide: MAT_STEPPER_GLOBAL_OPTIONS,
|
||||
useValue: {displayDefaultIndicatorType: false}
|
||||
}],
|
||||
[MatFormFieldModule, MatInputModule]
|
||||
);
|
||||
fixture.detectChanges();
|
||||
stepper = fixture.debugElement
|
||||
.query(By.css('mat-horizontal-stepper')).componentInstance;
|
||||
});
|
||||
|
||||
it('should show done state when step is completed and its not the current step', () => {
|
||||
let nextButtonNativeEl = fixture.debugElement
|
||||
.queryAll(By.directive(MatStepperNext))[0].nativeElement;
|
||||
|
||||
stepper.selectedIndex = 1;
|
||||
stepper._steps.first.completed = true;
|
||||
nextButtonNativeEl.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(stepper._getIndicatorType(0)).toBe(STEP_STATE.DONE);
|
||||
});
|
||||
|
||||
it('should show edit state when step is editable and its the current step', () => {
|
||||
stepper.selectedIndex = 1;
|
||||
stepper._steps.toArray()[1].editable = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(stepper._getIndicatorType(1)).toBe(STEP_STATE.EDIT);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/** Asserts that keyboard interaction works correctly. */
|
||||
@@ -1012,6 +1072,67 @@ function asyncValidator(minLength: number, validationTrigger: Subject<void>): As
|
||||
};
|
||||
}
|
||||
|
||||
function createComponent<T>(component: Type<T>,
|
||||
providers: Provider[] = [],
|
||||
imports: any[] = []): ComponentFixture<T> {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MatStepperModule,
|
||||
NoopAnimationsModule,
|
||||
ReactiveFormsModule,
|
||||
...imports
|
||||
],
|
||||
declarations: [component],
|
||||
providers: [
|
||||
{provide: Directionality, useFactory: () => dir},
|
||||
...providers
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
return TestBed.createComponent<T>(component);
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<form [formGroup]="formGroup">
|
||||
<mat-horizontal-stepper>
|
||||
<mat-step errorMessage="This field is required" [stepControl]="formArray?.get([0])">
|
||||
<ng-template matStepLabel>Step 1</ng-template>
|
||||
<mat-form-field>
|
||||
<mat-label>First name</mat-label>
|
||||
<input matInput formControlName="firstNameCtrl" required>
|
||||
<mat-error>This field is required</mat-error>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Step 2</ng-template>
|
||||
Content 2
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
</form>
|
||||
`
|
||||
})
|
||||
class MatHorizontalStepperWithErrorsApp implements OnInit {
|
||||
formGroup: FormGroup;
|
||||
|
||||
constructor(private _formBuilder: FormBuilder) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.formGroup = this._formBuilder.group({
|
||||
firstNameCtrl: ['', Validators.required],
|
||||
lastNameCtrl: ['', Validators.required],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<mat-horizontal-stepper>
|
||||
|
||||
+12
-11
@@ -7,7 +7,13 @@
|
||||
*/
|
||||
|
||||
import {Directionality} from '@angular/cdk/bidi';
|
||||
import {CdkStep, CdkStepper, StepContentPositionState} from '@angular/cdk/stepper';
|
||||
import {
|
||||
CdkStep,
|
||||
CdkStepper,
|
||||
StepContentPositionState,
|
||||
MAT_STEPPER_GLOBAL_OPTIONS,
|
||||
StepperOptions
|
||||
} from '@angular/cdk/stepper';
|
||||
import {AnimationEvent} from '@angular/animations';
|
||||
import {
|
||||
AfterContentInit,
|
||||
@@ -54,9 +60,11 @@ export class MatStep extends CdkStep implements ErrorStateMatcher {
|
||||
/** Content for step label given by `<ng-template matStepLabel>`. */
|
||||
@ContentChild(MatStepLabel) stepLabel: MatStepLabel;
|
||||
|
||||
/** @breaking-change 8.0.0 remove the `?` after `stepperOptions` */
|
||||
constructor(@Inject(forwardRef(() => MatStepper)) stepper: MatStepper,
|
||||
@SkipSelf() private _errorStateMatcher: ErrorStateMatcher) {
|
||||
super(stepper);
|
||||
@SkipSelf() private _errorStateMatcher: ErrorStateMatcher,
|
||||
@Optional() @Inject(MAT_STEPPER_GLOBAL_OPTIONS) stepperOptions?: StepperOptions) {
|
||||
super(stepper, stepperOptions);
|
||||
}
|
||||
|
||||
/** Custom error state matcher that additionally checks for validity of interacted form. */
|
||||
@@ -94,14 +102,7 @@ export class MatStepper extends _CdkStepper implements AfterContentInit {
|
||||
|
||||
ngAfterContentInit() {
|
||||
const icons = this._icons.toArray();
|
||||
|
||||
['edit', 'done', 'number'].forEach(name => {
|
||||
const override = icons.find(icon => icon.name === name);
|
||||
|
||||
if (override) {
|
||||
this._iconOverrides[name] = override.templateRef;
|
||||
}
|
||||
});
|
||||
icons.forEach(({name, templateRef}) => this._iconOverrides[name] = templateRef);
|
||||
|
||||
// Mark the component for change detection whenever the content children query changes
|
||||
this._steps.changes.pipe(takeUntil(this._destroyed)).subscribe(() => this._stateChanged());
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<mat-horizontal-stepper linear #stepper>
|
||||
<mat-step [stepControl]="firstFormGroup" errorMessage="Name is required.">
|
||||
<form [formGroup]="firstFormGroup">
|
||||
<ng-template matStepLabel>Fill out your name</ng-template>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="secondFormGroup" errorMessage="Address is required.">
|
||||
<form [formGroup]="secondFormGroup">
|
||||
<ng-template matStepLabel>Fill out your address</ng-template>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Address" formControlName="secondCtrl" required>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Done</ng-template>
|
||||
You are now done.
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button (click)="stepper.reset()">Reset</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
@@ -0,0 +1,30 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||
import {MAT_STEPPER_GLOBAL_OPTIONS} from '@angular/cdk/stepper';
|
||||
|
||||
/**
|
||||
* @title Stepper that displays errors in the steps
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stepper-errors-example',
|
||||
templateUrl: 'stepper-errors-example.html',
|
||||
styleUrls: ['stepper-errors-example.css'],
|
||||
providers: [{
|
||||
provide: MAT_STEPPER_GLOBAL_OPTIONS, useValue: {showError: true}
|
||||
}]
|
||||
})
|
||||
export class StepperErrorsExample implements OnInit {
|
||||
firstFormGroup: FormGroup;
|
||||
secondFormGroup: FormGroup;
|
||||
|
||||
constructor(private _formBuilder: FormBuilder) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.firstFormGroup = this._formBuilder.group({
|
||||
firstCtrl: ['', Validators.required]
|
||||
});
|
||||
this.secondFormGroup = this._formBuilder.group({
|
||||
secondCtrl: ['', Validators.required]
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<mat-horizontal-stepper #stepper>
|
||||
<mat-step [stepControl]="firstFormGroup">
|
||||
<form [formGroup]="firstFormGroup">
|
||||
<ng-template matStepLabel>Fill out your name</ng-template>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="secondFormGroup">
|
||||
<form [formGroup]="secondFormGroup">
|
||||
<ng-template matStepLabel>Fill out your address</ng-template>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Address" formControlName="secondCtrl" required>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Done</ng-template>
|
||||
You are now done.
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button (click)="stepper.reset()">Reset</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
</mat-horizontal-stepper>
|
||||
|
||||
<mat-horizontal-stepper>
|
||||
<mat-step label="Step 1" state="phone">
|
||||
<p>Put down your phones.</p>
|
||||
<div>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<mat-step label="Step 2" state="chat">
|
||||
<p>Socialize with each other.</p>
|
||||
<div>
|
||||
<button mat-button matStepperPrevious>Back</button>
|
||||
<button mat-button matStepperNext>Next</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
<mat-step label="Step 3">
|
||||
<p>You're welcome.</p>
|
||||
</mat-step>
|
||||
|
||||
<!-- Icon overrides. -->
|
||||
<ng-template matStepperIcon="phone">
|
||||
<mat-icon>call_end</mat-icon>
|
||||
</ng-template>
|
||||
<ng-template matStepperIcon="chat">
|
||||
<mat-icon>forum</mat-icon>
|
||||
</ng-template>
|
||||
</mat-horizontal-stepper>
|
||||
@@ -0,0 +1,30 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||
import {MAT_STEPPER_GLOBAL_OPTIONS} from '@angular/cdk/stepper';
|
||||
|
||||
/**
|
||||
* @title Stepper with customized states
|
||||
*/
|
||||
@Component({
|
||||
selector: 'stepper-states-example',
|
||||
templateUrl: 'stepper-states-example.html',
|
||||
styleUrls: ['stepper-states-example.css'],
|
||||
providers: [{
|
||||
provide: MAT_STEPPER_GLOBAL_OPTIONS, useValue: {displayDefaultIndicatorType: false}
|
||||
}]
|
||||
})
|
||||
export class StepperStatesExample implements OnInit {
|
||||
firstFormGroup: FormGroup;
|
||||
secondFormGroup: FormGroup;
|
||||
|
||||
constructor(private _formBuilder: FormBuilder) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.firstFormGroup = this._formBuilder.group({
|
||||
firstCtrl: ['', Validators.required]
|
||||
});
|
||||
this.secondFormGroup = this._formBuilder.group({
|
||||
secondCtrl: ['', Validators.required]
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user