Skip to content

feat(stepper): Create MAT_STEPPER_GLOBAL_OPTIONS InjectionToken #11457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Sep 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8712df9
feat(stepper): add state input to step
arodr967 May 17, 2018
48b3f1f
feat(stepper): add state input to step
arodr967 May 17, 2018
4adb591
feat(stepper): add showError Input, update _getIndicatorType logic, a…
arodr967 Jul 27, 2018
beda45f
feat(stepper): create stepper global options and update logic in _get…
arodr967 Aug 6, 2018
1cc2f91
test(stepper): revert unit and e2e tests
arodr967 Aug 6, 2018
4d671f6
refactor(stepper-demo): revert to previous state
arodr967 Aug 6, 2018
820dc54
refactor(stepper): fix lint issues
arodr967 Aug 6, 2018
b3d5134
refactor(stepper): parameter formatting and fix typo
arodr967 Aug 9, 2018
fdbf799
test(stepper): refactor stepper unit tests and add new tests
arodr967 Aug 10, 2018
ebf59a4
docs(stepper): add info about MAT_STEPPER_GLOBAL_OPTIONS
arodr967 Aug 10, 2018
f1e3b02
refactor(stepper): remove extra string from some types using StepState
arodr967 Aug 10, 2018
9f48437
refactor(stepper): rename useGuidelines to displayDefaultIndicatorTyp…
arodr967 Aug 13, 2018
726ce77
docs(stepper): add embedded examples
arodr967 Aug 17, 2018
ed4e121
fix(stepper): make stepperOptions an optional parameter
arodr967 Aug 23, 2018
4e952fe
refactor(stepper): rename mat-step-icon-not-selected class to mat-ste…
arodr967 Aug 24, 2018
ea4ded3
feat(stepper): only show step error after it has been interacted with
arodr967 Aug 24, 2018
b3d11ef
fix(stepper): add back both not-touched and not-selected css classes
arodr967 Aug 24, 2018
e5bceb3
fix(stepper): add state specific css classes
arodr967 Sep 5, 2018
f2ccadc
refactor(stepper): use not selector instead of extra css class
arodr967 Sep 6, 2018
8cd1910
fix(stepper): use css specificity in order to remove the use of impor…
arodr967 Sep 6, 2018
10fc780
remove -not-selected class
mmalerba Sep 6, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 105 additions & 7 deletions src/cdk/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
TemplateRef,
ViewChild,
ViewEncapsulation,
InjectionToken,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {AbstractControl} from '@angular/forms';
Expand Down Expand Up @@ -65,6 +66,37 @@ export class StepperSelectionEvent {
previouslySelectedStep: CdkStep;
}

/** The state of each step. */
export type StepState = 'number' | 'edit' | 'done' | 'error' | string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this just the same as string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but like this it is clear to the developer that the default states that we are using are 'number' | 'edit' | 'done' | 'error' and that the developer also has the option to define their own state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can reference the enum here instead of repeating all the literals STEP_STATE | 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',
Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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; }
Expand All @@ -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 {
Expand All @@ -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();
}
Expand Down Expand Up @@ -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 ? 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 step.editable ? 'edit' : 'done';
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;
Expand Down
26 changes: 23 additions & 3 deletions src/lib/stepper/_stepper-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,18 +21,29 @@
}

.mat-step-icon {
background-color: mat-color($primary);
background-color: mat-color($foreground, disabled-text);
color: mat-color($primary, default-contrast);
}

.mat-step-icon-not-touched {
background-color: mat-color($foreground, disabled-text);
.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 {
Expand Down Expand Up @@ -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);
Expand Down
29 changes: 23 additions & 6 deletions src/lib/stepper/step-header.html
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -26,16 +24,35 @@
[ngTemplateOutletContext]="_getIconContext()"></ng-container>
<mat-icon *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>

16 changes: 11 additions & 5 deletions src/lib/stepper/step-header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -38,6 +38,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;
Expand Down
7 changes: 5 additions & 2 deletions src/lib/stepper/step-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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>};

Expand Down
3 changes: 2 additions & 1 deletion src/lib/stepper/stepper-horizontal.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
3 changes: 2 additions & 1 deletion src/lib/stepper/stepper-icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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>) {}
}
3 changes: 2 additions & 1 deletion src/lib/stepper/stepper-vertical.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
Loading