From af7d7253afb3213d55a2a07e1f9972baeda347c5 Mon Sep 17 00:00:00 2001 From: Ji Won Shin Date: Wed, 16 Aug 2017 14:20:31 -0700 Subject: [PATCH 1/8] Additional properties for step --- src/cdk/stepper/stepper.ts | 23 ++++++++++++++++++++- src/demo-app/stepper/stepper-demo.html | 19 ++++++----------- src/demo-app/stepper/stepper-demo.ts | 3 ++- src/lib/stepper/_stepper-theme.scss | 12 ++++++++--- src/lib/stepper/index.ts | 10 ++++++++- src/lib/stepper/stepper-horizontal.html | 13 ++++++++++-- src/lib/stepper/stepper-vertical.html | 15 +++++++++++--- src/lib/stepper/stepper.scss | 27 +++++++++++++++++-------- 8 files changed, 90 insertions(+), 32 deletions(-) diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index 9bb879647d7e..bd173386e325 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -77,6 +77,26 @@ export class CdkStep { @Input() label: string; + @Input() + get editable() { return this._editable; } + set editable(value: any) { + this._editable = coerceBooleanProperty(value); + } + private _editable = true; + + /** Whether the completion of step is optional or not. */ + @Input() + get optional() { return this._optional; } + set optional(value: any) { + this._optional = coerceBooleanProperty(value); + } + private _optional = false; + + /** Return whether step is completed or not. */ + get completed() { + return this._stepControl ? this._stepControl.valid && this.interacted : this.interacted; + } + constructor(private _stepper: CdkStepper) { } /** Selects this step component. */ @@ -109,6 +129,7 @@ export class CdkStepper { @Input() get selectedIndex() { return this._selectedIndex; } set selectedIndex(index: number) { + if (index < this._selectedIndex && !this._steps.toArray()[index].editable) { return; } if (this._selectedIndex != index && !this._anyControlsInvalid(index)) { this._emitStepperSelectionEvent(index); this._focusStep(this._selectedIndex); @@ -208,7 +229,7 @@ export class CdkStepper { const stepsArray = this._steps.toArray(); stepsArray[this._selectedIndex].interacted = true; if (this._linear) { - return stepsArray.slice(0, index).some(step => step.stepControl.invalid); + return stepsArray.slice(0, index).some(step => step.stepControl.invalid && !step.optional); } return false; } diff --git a/src/demo-app/stepper/stepper-demo.html b/src/demo-app/stepper/stepper-demo.html index 70dd6ab6b81b..787b96be930a 100644 --- a/src/demo-app/stepper/stepper-demo.html +++ b/src/demo-app/stepper/stepper-demo.html @@ -19,12 +19,12 @@

Linear Vertical Stepper Demo using a single form

- +
Fill out your phone number
- + This field is required
@@ -88,30 +88,28 @@

Linear Horizontal Stepper Demo using a different form for each step

Vertical Stepper Demo

+Make steps non-editable - + Fill out your name - This field is required - This field is required
- +
Fill out your phone number
- This field is required
@@ -119,13 +117,12 @@

Vertical Stepper Demo

- +
Fill out your address
- This field is required
@@ -148,12 +145,10 @@

Horizontal Stepper Demo

Fill out your name - This field is required - This field is required
@@ -166,7 +161,6 @@

Horizontal Stepper Demo

- This field is required
@@ -180,7 +174,6 @@

Horizontal Stepper Demo

- This field is required
diff --git a/src/demo-app/stepper/stepper-demo.ts b/src/demo-app/stepper/stepper-demo.ts index c4f546f0dfed..d220a99273a2 100644 --- a/src/demo-app/stepper/stepper-demo.ts +++ b/src/demo-app/stepper/stepper-demo.ts @@ -10,6 +10,7 @@ import {FormBuilder, FormGroup, Validators} from '@angular/forms'; export class StepperDemo { formGroup: FormGroup; isNonLinear = false; + isNonEditable = false; nameFormGroup: FormGroup; phoneFormGroup: FormGroup; @@ -34,7 +35,7 @@ export class StepperDemo { lastNameFormCtrl: ['', Validators.required], }), this._formBuilder.group({ - phoneFormCtrl: [''], + phoneFormCtrl: ['', Validators.required], }) ]) }); diff --git a/src/lib/stepper/_stepper-theme.scss b/src/lib/stepper/_stepper-theme.scss index 4f72d6ab0f48..d0dc72e02c29 100644 --- a/src/lib/stepper/_stepper-theme.scss +++ b/src/lib/stepper/_stepper-theme.scss @@ -9,11 +9,17 @@ .mat-horizontal-stepper-header, .mat-vertical-stepper-header { - .mat-stepper-label { + .mat-stepper-label-active { color: mat-color($foreground, text); } - .mat-stepper-index { + .mat-stepper-label-inactive, + .mat-step-optional { + color: mat-color($foreground, disabled-text); + } + + .mat-stepper-index-interacted, + .mat-stepper-index-new { background-color: mat-color($primary); color: mat-color($primary, default-contrast); } @@ -23,7 +29,7 @@ color: mat-color($foreground, disabled-text); } - .mat-stepper-index { + .mat-stepper-index-new { background-color: mat-color($foreground, disabled-text); } } diff --git a/src/lib/stepper/index.ts b/src/lib/stepper/index.ts index de1802347449..4f52be27b607 100644 --- a/src/lib/stepper/index.ts +++ b/src/lib/stepper/index.ts @@ -17,9 +17,17 @@ import {CdkStepperModule} from '@angular/cdk/stepper'; import {MdCommonModule} from '../core'; import {MdStepLabel} from './step-label'; import {MdStepperNext, MdStepperPrevious} from './stepper-button'; +import {MdIconModule} from '../icon/index'; @NgModule({ - imports: [MdCommonModule, CommonModule, PortalModule, MdButtonModule, CdkStepperModule], + imports: [ + MdCommonModule, + CommonModule, + PortalModule, + MdButtonModule, + CdkStepperModule, + MdIconModule + ], exports: [MdCommonModule, MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper, MdStepperNext, MdStepperPrevious], declarations: [MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper, diff --git a/src/lib/stepper/stepper-horizontal.html b/src/lib/stepper/stepper-horizontal.html index 0eade60c9f35..fe5fbf10c726 100644 --- a/src/lib/stepper/stepper-horizontal.html +++ b/src/lib/stepper/stepper-horizontal.html @@ -8,16 +8,25 @@ [tabIndex]="_focusIndex == i ? 0 : -1" (click)="step.select()" (keydown)="_onKeydown($event)"> -
+
{{i + 1}}
+
+ done + create +
-
+
{{step.label}}
+ +
Optional
diff --git a/src/lib/stepper/stepper-vertical.html b/src/lib/stepper/stepper-vertical.html index 8efe7a283932..c05beccfaa8e 100644 --- a/src/lib/stepper/stepper-vertical.html +++ b/src/lib/stepper/stepper-vertical.html @@ -6,19 +6,28 @@ [tabIndex]="_focusIndex == i ? 0 : -1" (click)="step.select()" (keydown)="_onKeydown($event)"> -
+
{{i + 1}}
+
+ done + create +
-
+
{{step.label}}
-
+
Optional
+
+
Date: Wed, 16 Aug 2017 15:50:01 -0700 Subject: [PATCH 2/8] Unit tests --- src/lib/stepper/stepper.spec.ts | 79 +++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index c7e478fa77bb..903674ad0b00 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -75,6 +75,16 @@ describe('MdHorizontalStepper', () => { let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header')); checkKeyboardEvent(stepperComponent, fixture, stepHeaders); }); + + it('should only be able to return to a previous step if it is editable', () => { + checkEditableStep(stepperComponent, fixture); + }); + + it('should show icon instead of index and active label on completed steps', () => { + let stepperComponentEl = fixture.debugElement + .query(By.css('md-horizontal-stepper')).nativeElement; + checkStepIcon(stepperComponent, stepperComponentEl, fixture); + }); }); describe('linear horizontal stepper', () => { @@ -105,6 +115,10 @@ describe('MdHorizontalStepper', () => { .queryAll(By.css('.mat-horizontal-stepper-header'))[1].nativeElement; checkLinearStepperValidity(stepHeaderEl, stepperComponent, testComponent, fixture); }); + + it('should be able to move to next step even when invalid if current step is optional', () => { + checkOptionalStep(stepperComponent, testComponent, fixture); + }); }); }); @@ -173,6 +187,16 @@ describe('MdVerticalStepper', () => { let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header')); checkKeyboardEvent(stepperComponent, fixture, stepHeaders); }); + + it('should only be able to return to a previous step if it is editable', () => { + checkEditableStep(stepperComponent, fixture); + }); + + it('should show icon instead of index on completed steps', () => { + let stepperComponentEl = fixture.debugElement + .query(By.css('md-vertical-stepper')).nativeElement; + checkStepIcon(stepperComponent, stepperComponentEl, fixture); + }); }); describe('linear vertical stepper', () => { @@ -204,6 +228,10 @@ describe('MdVerticalStepper', () => { checkLinearStepperValidity(stepHeaderEl, stepperComponent, testComponent, fixture); }); + + it('should be able to move to next step even when invalid if current step is optional', () => { + checkOptionalStep(stepperComponent, testComponent, fixture); + }); }); }); @@ -412,6 +440,57 @@ function checkLinearStepperValidity(stepHeaderEl: HTMLElement, expect(stepperComponent.selectedIndex).toBe(1); } +function checkEditableStep(stepperComponent: MdStepper, + fixture: ComponentFixture) { + stepperComponent.selectedIndex = 1; + stepperComponent._steps.toArray()[0].editable = false; + let previousButtonNativeEl = fixture.debugElement + .queryAll(By.directive(MdStepperPrevious))[1].nativeElement; + previousButtonNativeEl.click(); + fixture.detectChanges(); + + expect(stepperComponent.selectedIndex).toBe(1); + + stepperComponent._steps.toArray()[0].editable = true; + previousButtonNativeEl.click(); + fixture.detectChanges(); + + expect(stepperComponent.selectedIndex).toBe(0); +} + +function checkOptionalStep(stepperComponent: MdStepper, + testComponent: LinearMdHorizontalStepperApp | LinearMdVerticalStepperApp, + fixture: ComponentFixture) { + expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe(''); + expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false); + expect(testComponent.oneGroup.valid).toBe(false); + expect(stepperComponent.selectedIndex).toBe(0); + + stepperComponent._steps.toArray()[0].optional = true; + let nextButtonNativeEl = fixture.debugElement + .queryAll(By.directive(MdStepperNext))[0].nativeElement; + nextButtonNativeEl.click(); + fixture.detectChanges(); + + expect(stepperComponent.selectedIndex).toBe(1); +} + +function checkStepIcon(stepperComponent: MdStepper, + stepperComponentEl: HTMLElement, + fixture: ComponentFixture) { + expect(stepperComponent._steps.toArray()[0].completed).toBe(false); + expect(stepperComponentEl.querySelectorAll('.mat-stepper-index-interacted').length).toBe(0); + expect(stepperComponentEl.querySelectorAll('.mat-stepper-label-active').length).toBe(1); + let nextButtonNativeEl = fixture.debugElement + .queryAll(By.directive(MdStepperNext))[0].nativeElement; + nextButtonNativeEl.click(); + fixture.detectChanges(); + + expect(stepperComponent._steps.toArray()[0].completed).toBe(true); + expect(stepperComponentEl.querySelectorAll('.mat-stepper-index-interacted').length).toBe(1); + expect(stepperComponentEl.querySelectorAll('.mat-stepper-label-active').length).toBe(2); +} + @Component({ template: ` From edd0a03f48102c95456be69de6c0e81130a9e589 Mon Sep 17 00:00:00 2001 From: Ji Won Shin Date: Thu, 17 Aug 2017 11:53:48 -0700 Subject: [PATCH 3/8] Code changes based on review + test name changes --- src/cdk/stepper/stepper.ts | 18 +++++ src/demo-app/stepper/stepper-demo.html | 4 +- src/lib/stepper/_stepper-theme.scss | 5 +- src/lib/stepper/stepper-horizontal.html | 18 ++--- src/lib/stepper/stepper-vertical.html | 18 ++--- src/lib/stepper/stepper.scss | 9 +-- src/lib/stepper/stepper.spec.ts | 103 ++++++++++++++---------- 7 files changed, 101 insertions(+), 74 deletions(-) diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index dbdb1cafa6f0..bd1d2e13a89b 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -93,7 +93,16 @@ export class CdkStep { private _optional = false; /** Return whether step is completed or not. */ + @Input() get completed() { + return this._customCompleted == null ? this._defaultCompleted : this._customCompleted; + } + set completed(value: any) { + this._customCompleted = coerceBooleanProperty(value); + } + private _customCompleted: boolean | null = null; + + private get _defaultCompleted() { return this._stepControl ? this._stepControl.valid && this.interacted : this.interacted; } @@ -193,6 +202,15 @@ export class CdkStepper { } } + _getIndicatorType(index: number): 'number' | 'edit' | 'done' { + const step = this._steps.toArray()[index]; + if (!step.completed || this._selectedIndex == index) { + return 'number'; + } else { + return step.editable ? 'edit' : 'done'; + } + } + private _emitStepperSelectionEvent(newIndex: number): void { const stepsArray = this._steps.toArray(); this.selectionChange.emit({ diff --git a/src/demo-app/stepper/stepper-demo.html b/src/demo-app/stepper/stepper-demo.html index 787b96be930a..aae8e205710f 100644 --- a/src/demo-app/stepper/stepper-demo.html +++ b/src/demo-app/stepper/stepper-demo.html @@ -19,7 +19,7 @@

Linear Vertical Stepper Demo using a single form

- +
Fill out your phone number
@@ -62,7 +62,7 @@

Linear Horizontal Stepper Demo using a different form for each step

- +
Fill out your phone number diff --git a/src/lib/stepper/_stepper-theme.scss b/src/lib/stepper/_stepper-theme.scss index 721382554362..22322282e874 100644 --- a/src/lib/stepper/_stepper-theme.scss +++ b/src/lib/stepper/_stepper-theme.scss @@ -23,8 +23,7 @@ color: mat-color($foreground, disabled-text); } - .mat-stepper-index-interacted, - .mat-stepper-index-new { + .mat-step-indicator { background-color: mat-color($primary); color: mat-color($primary, default-contrast); } @@ -34,7 +33,7 @@ color: mat-color($foreground, disabled-text); } - .mat-stepper-index-new { + .mat-step-indicator-not-touched { background-color: mat-color($foreground, disabled-text); } } diff --git a/src/lib/stepper/stepper-horizontal.html b/src/lib/stepper/stepper-horizontal.html index cb5a2ae01c85..2108923513a1 100644 --- a/src/lib/stepper/stepper-horizontal.html +++ b/src/lib/stepper/stepper-horizontal.html @@ -8,18 +8,16 @@ [tabIndex]="_focusIndex == i ? 0 : -1" (click)="step.select()" (keydown)="_onKeydown($event)"> -
- {{i + 1}} -
-
- done - create +
+ {{i + 1}} + create + done
-
+
diff --git a/src/lib/stepper/stepper-vertical.html b/src/lib/stepper/stepper-vertical.html index 3c0788f7515b..d89772552375 100644 --- a/src/lib/stepper/stepper-vertical.html +++ b/src/lib/stepper/stepper-vertical.html @@ -6,18 +6,16 @@ [tabIndex]="_focusIndex == i ? 0 : -1" (click)="step.select()" (keydown)="_onKeydown($event)"> -
- {{i + 1}} -
-
- done - create +
+ {{i + 1}} + create + done
-
+
diff --git a/src/lib/stepper/stepper.scss b/src/lib/stepper/stepper.scss index ffa963af1d99..6068ea634041 100644 --- a/src/lib/stepper/stepper.scss +++ b/src/lib/stepper/stepper.scss @@ -24,8 +24,7 @@ $mat-step-optional-font-size: 12px; vertical-align: middle; } -.mat-stepper-index-new , -.mat-stepper-index-interacted { +.mat-step-indicator { border-radius: 50%; height: $mat-stepper-label-header-height; width: $mat-stepper-label-header-height; @@ -48,8 +47,7 @@ $mat-step-optional-font-size: 12px; outline: none; padding: 0 $mat-stepper-side-gap; - .mat-stepper-index-new , - .mat-stepper-index-interacted { + .mat-step-indicator { margin-right: $mat-stepper-line-gap; flex: none; } @@ -62,8 +60,7 @@ $mat-step-optional-font-size: 12px; outline: none; max-height: $mat-stepper-label-header-height; - .mat-stepper-index-new , - .mat-stepper-index-interacted { + .mat-step-indicator { margin-right: $mat-vertical-stepper-content-margin - $mat-stepper-side-gap; } } diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index 1721bfefa0c8..d96b723d555b 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -42,7 +42,7 @@ describe('MdHorizontalStepper', () => { it('should change selected index on header click', () => { let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header')); - checkSelectionChangeOnHeaderClick(stepperComponent, fixture, stepHeaders); + assertSelectionChangeOnHeaderClick(stepperComponent, fixture, stepHeaders); }); it('should set the "tablist" role on stepper', () => { @@ -52,44 +52,44 @@ describe('MdHorizontalStepper', () => { it('should set aria-expanded of content correctly', () => { let stepContents = fixture.debugElement.queryAll(By.css(`.mat-horizontal-stepper-content`)); - checkExpandedContent(stepperComponent, fixture, stepContents); + assertCorrectAriaExpandedAttribute(stepperComponent, fixture, stepContents); }); it('should display the correct label', () => { - checkCorrectLabel(stepperComponent, fixture); + assertCorrectStepLabel(stepperComponent, fixture); }); it('should go to next available step when the next button is clicked', () => { - checkNextStepperButton(stepperComponent, fixture); + assertNextStepperButtonClick(stepperComponent, fixture); }); it('should go to previous available step when the previous button is clicked', () => { - checkPreviousStepperButton(stepperComponent, fixture); + assertPreviousStepperButtonClick(stepperComponent, fixture); }); it('should set the correct step position for animation', () => { - checkStepPosition(stepperComponent, fixture); + assertCorrectStepPosition(stepperComponent, fixture); }); it('should support keyboard events to move and select focus', () => { let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header')); - checkKeyboardEvent(stepperComponent, fixture, stepHeaders); + assertCorrectKeyboardInteraction(stepperComponent, fixture, stepHeaders); }); it('should not set focus on header of selected step if header is not clicked', () => { let stepHeaderEl = fixture.debugElement .queryAll(By.css('.mat-horizontal-stepper-header'))[1].nativeElement; - checkStepHeaderFocusNotCalled(stepHeaderEl, stepperComponent, fixture); + assertStepHeaderFocusNotCalled(stepHeaderEl, stepperComponent, fixture); }); it('should only be able to return to a previous step if it is editable', () => { - checkEditableStep(stepperComponent, fixture); + assertEditableStepChange(stepperComponent, fixture); }); it('should show icon instead of index and active label on completed steps', () => { let stepperComponentEl = fixture.debugElement .query(By.css('md-horizontal-stepper')).nativeElement; - checkStepIcon(stepperComponent, stepperComponentEl, fixture); + assertCorrectStepIcon(stepperComponent, stepperComponentEl, fixture); }); }); @@ -119,17 +119,17 @@ describe('MdHorizontalStepper', () => { let stepHeaderEl = fixture.debugElement .queryAll(By.css('.mat-horizontal-stepper-header'))[1].nativeElement; - checkLinearStepperValidity(stepHeaderEl, stepperComponent, testComponent, fixture); + assertLinearStepperValidity(stepHeaderEl, stepperComponent, testComponent, fixture); }); it('should not focus step header upon click if it is not able to be selected', () => { let stepHeaderEl = fixture.debugElement .queryAll(By.css('.mat-horizontal-stepper-header'))[1].nativeElement; - checkStepHeaderBlur(stepHeaderEl, fixture); + assertStepHeaderBlurred(stepHeaderEl, fixture); }); it('should be able to move to next step even when invalid if current step is optional', () => { - checkOptionalStep(stepperComponent, testComponent, fixture); + assertOptionalStepValidity(stepperComponent, testComponent, fixture); }); }); }); @@ -165,7 +165,7 @@ describe('MdVerticalStepper', () => { it('should change selected index on header click', () => { let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header')); - checkSelectionChangeOnHeaderClick(stepperComponent, fixture, stepHeaders); + assertSelectionChangeOnHeaderClick(stepperComponent, fixture, stepHeaders); }); @@ -176,44 +176,44 @@ describe('MdVerticalStepper', () => { it('should set aria-expanded of content correctly', () => { let stepContents = fixture.debugElement.queryAll(By.css(`.mat-vertical-stepper-content`)); - checkExpandedContent(stepperComponent, fixture, stepContents); + assertCorrectAriaExpandedAttribute(stepperComponent, fixture, stepContents); }); it('should display the correct label', () => { - checkCorrectLabel(stepperComponent, fixture); + assertCorrectStepLabel(stepperComponent, fixture); }); it('should go to next available step when the next button is clicked', () => { - checkNextStepperButton(stepperComponent, fixture); + assertNextStepperButtonClick(stepperComponent, fixture); }); it('should go to previous available step when the previous button is clicked', () => { - checkPreviousStepperButton(stepperComponent, fixture); + assertPreviousStepperButtonClick(stepperComponent, fixture); }); it('should set the correct step position for animation', () => { - checkStepPosition(stepperComponent, fixture); + assertCorrectStepPosition(stepperComponent, fixture); }); it('should support keyboard events to move and select focus', () => { let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header')); - checkKeyboardEvent(stepperComponent, fixture, stepHeaders); + assertCorrectKeyboardInteraction(stepperComponent, fixture, stepHeaders); }); it('should not set focus on header of selected step if header is not clicked', () => { let stepHeaderEl = fixture.debugElement .queryAll(By.css('.mat-vertical-stepper-header'))[1].nativeElement; - checkStepHeaderFocusNotCalled(stepHeaderEl, stepperComponent, fixture); + assertStepHeaderFocusNotCalled(stepHeaderEl, stepperComponent, fixture); }); it('should only be able to return to a previous step if it is editable', () => { - checkEditableStep(stepperComponent, fixture); + assertEditableStepChange(stepperComponent, fixture); }); it('should show icon instead of index on completed steps', () => { let stepperComponentEl = fixture.debugElement .query(By.css('md-vertical-stepper')).nativeElement; - checkStepIcon(stepperComponent, stepperComponentEl, fixture); + assertCorrectStepIcon(stepperComponent, stepperComponentEl, fixture); }); }); @@ -244,22 +244,23 @@ describe('MdVerticalStepper', () => { let stepHeaderEl = fixture.debugElement .queryAll(By.css('.mat-vertical-stepper-header'))[1].nativeElement; - checkLinearStepperValidity(stepHeaderEl, stepperComponent, testComponent, fixture); + assertLinearStepperValidity(stepHeaderEl, stepperComponent, testComponent, fixture); }); it('should not focus step header upon click if it is not able to be selected', () => { let stepHeaderEl = fixture.debugElement .queryAll(By.css('.mat-vertical-stepper-header'))[1].nativeElement; - checkStepHeaderBlur(stepHeaderEl, fixture); + assertStepHeaderBlurred(stepHeaderEl, fixture); }); it('should be able to move to next step even when invalid if current step is optional', () => { - checkOptionalStep(stepperComponent, testComponent, fixture); + assertOptionalStepValidity(stepperComponent, testComponent, fixture); }); }); }); -function checkSelectionChangeOnHeaderClick(stepperComponent: MdStepper, +/** Asserts that `selectedIndex` updates correctly when header of another step is clicked. */ +function assertSelectionChangeOnHeaderClick(stepperComponent: MdStepper, fixture: ComponentFixture, stepHeaders: DebugElement[]) { expect(stepperComponent.selectedIndex).toBe(0); @@ -279,7 +280,8 @@ function checkSelectionChangeOnHeaderClick(stepperComponent: MdStepper, expect(stepperComponent.selectedIndex).toBe(2); } -function checkExpandedContent(stepperComponent: MdStepper, +/** Asserts that 'aria-expanded' attribute is correct for expanded content of step. */ +function assertCorrectAriaExpandedAttribute(stepperComponent: MdStepper, fixture: ComponentFixture, stepContents: DebugElement[]) { let firstStepContentEl = stepContents[0].nativeElement; @@ -293,7 +295,8 @@ function checkExpandedContent(stepperComponent: MdStepper, expect(secondStepContentEl.getAttribute('aria-expanded')).toBe('true'); } -function checkCorrectLabel(stepperComponent: MdStepper, fixture: ComponentFixture) { +/** Asserts that step has correct label. */ +function assertCorrectStepLabel(stepperComponent: MdStepper, fixture: ComponentFixture) { let selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]'); expect(selectedLabel.textContent).toMatch('Step 1'); @@ -310,7 +313,8 @@ function checkCorrectLabel(stepperComponent: MdStepper, fixture: ComponentFixtur expect(selectedLabel.textContent).toMatch('New Label'); } -function checkNextStepperButton(stepperComponent: MdStepper, fixture: ComponentFixture) { +/** Asserts that clicking on MdStepperNext button updates `selectedIndex` correctly. */ +function assertNextStepperButtonClick(stepperComponent: MdStepper, fixture: ComponentFixture) { expect(stepperComponent.selectedIndex).toBe(0); let nextButtonNativeEl = fixture.debugElement @@ -335,7 +339,9 @@ function checkNextStepperButton(stepperComponent: MdStepper, fixture: ComponentF expect(stepperComponent.selectedIndex).toBe(2); } -function checkPreviousStepperButton(stepperComponent: MdStepper, fixture: ComponentFixture) { +/** Asserts that clicking on MdStepperPrevious button updates `selectedIndex` correctly. */ +function assertPreviousStepperButtonClick(stepperComponent: MdStepper, + fixture: ComponentFixture) { expect(stepperComponent.selectedIndex).toBe(0); stepperComponent.selectedIndex = 2; @@ -361,7 +367,8 @@ function checkPreviousStepperButton(stepperComponent: MdStepper, fixture: Compon expect(stepperComponent.selectedIndex).toBe(0); } -function checkStepPosition(stepperComponent: MdStepper, fixture: ComponentFixture) { +/** Asserts that step position is correct for animation. */ +function assertCorrectStepPosition(stepperComponent: MdStepper, fixture: ComponentFixture) { expect(stepperComponent._getAnimationDirection(0)).toBe('current'); expect(stepperComponent._getAnimationDirection(1)).toBe('next'); expect(stepperComponent._getAnimationDirection(2)).toBe('next'); @@ -381,7 +388,8 @@ function checkStepPosition(stepperComponent: MdStepper, fixture: ComponentFixtur expect(stepperComponent._getAnimationDirection(2)).toBe('current'); } -function checkKeyboardEvent(stepperComponent: MdStepper, +/** Asserts that keyboard interaction works correctly. */ +function assertCorrectKeyboardInteraction(stepperComponent: MdStepper, fixture: ComponentFixture, stepHeaders: DebugElement[]) { expect(stepperComponent._focusIndex).toBe(0); @@ -439,7 +447,8 @@ function checkKeyboardEvent(stepperComponent: MdStepper, 'Expected index of selected step to change to index of focused step after SPACE event.'); } -function checkStepHeaderFocusNotCalled(stepHeaderEl: HTMLElement, +/** Asserts that step selection change using stepper buttons does not focus step header. */ +function assertStepHeaderFocusNotCalled(stepHeaderEl: HTMLElement, stepperComponent: MdStepper, fixture: ComponentFixture) { let nextButtonNativeEl = fixture.debugElement @@ -452,7 +461,10 @@ function checkStepHeaderFocusNotCalled(stepHeaderEl: HTMLElement, expect(stepHeaderEl.focus).not.toHaveBeenCalled(); } -function checkLinearStepperValidity(stepHeaderEl: HTMLElement, +/** + * Asserts that linear stepper does not allow step selection change if current step is not valid. + */ +function assertLinearStepperValidity(stepHeaderEl: HTMLElement, stepperComponent: MdStepper, testComponent: LinearMdHorizontalStepperApp | LinearMdVerticalStepperApp, @@ -477,7 +489,8 @@ function checkLinearStepperValidity(stepHeaderEl: HTMLElement, expect(stepperComponent.selectedIndex).toBe(1); } -function checkStepHeaderBlur(stepHeaderEl: HTMLElement, fixture: ComponentFixture) { +/** Asserts that step header focus is blurred if the step cannot be selected upon header click. */ +function assertStepHeaderBlurred(stepHeaderEl: HTMLElement, fixture: ComponentFixture) { spyOn(stepHeaderEl, 'blur'); stepHeaderEl.click(); fixture.detectChanges(); @@ -485,7 +498,8 @@ function checkStepHeaderBlur(stepHeaderEl: HTMLElement, fixture: ComponentFixtur expect(stepHeaderEl.blur).toHaveBeenCalled(); } -function checkEditableStep(stepperComponent: MdStepper, +/** Asserts that it is only possible to go back to a previous step if the step is editable. */ +function assertEditableStepChange(stepperComponent: MdStepper, fixture: ComponentFixture) { stepperComponent.selectedIndex = 1; stepperComponent._steps.toArray()[0].editable = false; @@ -503,7 +517,8 @@ function checkEditableStep(stepperComponent: MdStepper, expect(stepperComponent.selectedIndex).toBe(0); } -function checkOptionalStep(stepperComponent: MdStepper, +/** Asserts that it is only possible to skip a step in linear stepper if the step is optional. */ +function assertOptionalStepValidity(stepperComponent: MdStepper, testComponent: LinearMdHorizontalStepperApp | LinearMdVerticalStepperApp, fixture: ComponentFixture) { expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe(''); @@ -520,19 +535,21 @@ function checkOptionalStep(stepperComponent: MdStepper, expect(stepperComponent.selectedIndex).toBe(1); } -function checkStepIcon(stepperComponent: MdStepper, +/** Asserts that the correct step indicator is set upon step selection change event. */ +function assertCorrectStepIcon(stepperComponent: MdStepper, stepperComponentEl: HTMLElement, fixture: ComponentFixture) { - expect(stepperComponent._steps.toArray()[0].completed).toBe(false); - expect(stepperComponentEl.querySelectorAll('.mat-stepper-index-interacted').length).toBe(0); + let stepsArray = stepperComponent._steps.toArray(); + expect(stepsArray[0].completed).toBe(false); + expect(stepperComponent._getIndicatorType(0)).toBe('number'); expect(stepperComponentEl.querySelectorAll('.mat-stepper-label-active').length).toBe(1); let nextButtonNativeEl = fixture.debugElement .queryAll(By.directive(MdStepperNext))[0].nativeElement; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent._steps.toArray()[0].completed).toBe(true); - expect(stepperComponentEl.querySelectorAll('.mat-stepper-index-interacted').length).toBe(1); + expect(stepsArray[0].completed).toBe(true); + expect(stepperComponent._getIndicatorType(0)).not.toBe('number'); expect(stepperComponentEl.querySelectorAll('.mat-stepper-label-active').length).toBe(2); } From b214f702cf88da1a58abf252147da2eced133026 Mon Sep 17 00:00:00 2001 From: Ji Won Shin Date: Thu, 17 Aug 2017 18:16:34 -0700 Subject: [PATCH 4/8] Refactor code for shared functionality between vertical and horizontal stepper --- src/cdk/stepper/public_api.ts | 10 ++- src/cdk/stepper/step-icon.ts | 41 +++++++++++ src/cdk/stepper/step-label-container.ts | 28 ++++++++ src/cdk/stepper/stepper.ts | 9 --- src/lib/stepper/_stepper-theme.scss | 4 +- src/lib/stepper/index.ts | 8 ++- src/lib/stepper/step-icon.html | 5 ++ src/lib/stepper/step-icon.spec.ts | 83 +++++++++++++++++++++++ src/lib/stepper/step-icon.ts | 20 ++++++ src/lib/stepper/step-label-container.html | 7 ++ src/lib/stepper/step-label-container.ts | 21 ++++++ src/lib/stepper/stepper-horizontal.html | 23 ++----- src/lib/stepper/stepper-vertical.html | 21 +----- src/lib/stepper/stepper.scss | 9 ++- src/lib/stepper/stepper.spec.ts | 30 -------- 15 files changed, 232 insertions(+), 87 deletions(-) create mode 100644 src/cdk/stepper/step-icon.ts create mode 100644 src/cdk/stepper/step-label-container.ts create mode 100644 src/lib/stepper/step-icon.html create mode 100644 src/lib/stepper/step-icon.spec.ts create mode 100644 src/lib/stepper/step-icon.ts create mode 100644 src/lib/stepper/step-label-container.html create mode 100644 src/lib/stepper/step-label-container.ts diff --git a/src/cdk/stepper/public_api.ts b/src/cdk/stepper/public_api.ts index cfb2fec4dc26..8989f3ee7df4 100644 --- a/src/cdk/stepper/public_api.ts +++ b/src/cdk/stepper/public_api.ts @@ -11,14 +11,20 @@ import {CdkStepper, CdkStep} from './stepper'; import {CommonModule} from '@angular/common'; import {CdkStepLabel} from './step-label'; import {CdkStepperNext, CdkStepperPrevious} from './stepper-button'; +import {CdkStepIcon} from './step-icon'; +import {CdkStepLabelContainer} from './step-label-container'; @NgModule({ imports: [CommonModule], - exports: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious], - declarations: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious] + exports: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious, CdkStepIcon, + CdkStepLabelContainer], + declarations: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious, CdkStepIcon, + CdkStepLabelContainer] }) export class CdkStepperModule {} export * from './stepper'; export * from './step-label'; export * from './stepper-button'; +export * from './step-icon'; +export * from './step-label-container'; diff --git a/src/cdk/stepper/step-icon.ts b/src/cdk/stepper/step-icon.ts new file mode 100644 index 000000000000..7fb2ba3e2d66 --- /dev/null +++ b/src/cdk/stepper/step-icon.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive, Input} from '@angular/core'; +import {CdkStep} from './stepper'; + +@Directive({ + selector: 'cdkStepIcon' +}) +export class CdkStepIcon { + /** Step of the icon to be displayed. */ + @Input() + step: CdkStep; + + /** Whether the step of the icon to be displayed is active. */ + @Input() + selected: boolean; + + /** Index of the step. */ + @Input() + index: number; + + /** Whether the user has touched the step that is not selected. */ + get notTouched() { + return this._getIndicatorType() == 'number' && !this.selected; + } + + /** Returns the type of icon to be displayed. */ + _getIndicatorType(): 'number' | 'edit' | 'done' { + if (!this.step.completed || this.selected) { + return 'number'; + } else { + return this.step.editable ? 'edit' : 'done'; + } + } +} diff --git a/src/cdk/stepper/step-label-container.ts b/src/cdk/stepper/step-label-container.ts new file mode 100644 index 000000000000..cec0f4320dee --- /dev/null +++ b/src/cdk/stepper/step-label-container.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive, Input} from '@angular/core'; +import {CdkStep} from './stepper'; + +@Directive({ + selector: 'cdkStepLabelContainer' +}) +export class CdkStepLabelContainer { + /** Step of the label to be displayed. */ + @Input() + step: CdkStep; + + /** Whether the step of label to be displayed is selected. */ + @Input() + selected: boolean; + + /** Whether the label to be displayed is active. */ + get active() { + return this.step.completed || this.selected; + } +} diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index bd1d2e13a89b..02b47b0ad2cb 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -202,15 +202,6 @@ export class CdkStepper { } } - _getIndicatorType(index: number): 'number' | 'edit' | 'done' { - const step = this._steps.toArray()[index]; - if (!step.completed || this._selectedIndex == index) { - return 'number'; - } else { - return step.editable ? 'edit' : 'done'; - } - } - private _emitStepperSelectionEvent(newIndex: number): void { const stepsArray = this._steps.toArray(); this.selectionChange.emit({ diff --git a/src/lib/stepper/_stepper-theme.scss b/src/lib/stepper/_stepper-theme.scss index 22322282e874..a2cd4cfb23b3 100644 --- a/src/lib/stepper/_stepper-theme.scss +++ b/src/lib/stepper/_stepper-theme.scss @@ -23,7 +23,7 @@ color: mat-color($foreground, disabled-text); } - .mat-step-indicator { + .mat-step-icon { background-color: mat-color($primary); color: mat-color($primary, default-contrast); } @@ -33,7 +33,7 @@ color: mat-color($foreground, disabled-text); } - .mat-step-indicator-not-touched { + .mat-step-icon-not-touched { background-color: mat-color($foreground, disabled-text); } } diff --git a/src/lib/stepper/index.ts b/src/lib/stepper/index.ts index 4f52be27b607..ebfbd989a2b6 100644 --- a/src/lib/stepper/index.ts +++ b/src/lib/stepper/index.ts @@ -18,6 +18,8 @@ import {MdCommonModule} from '../core'; import {MdStepLabel} from './step-label'; import {MdStepperNext, MdStepperPrevious} from './stepper-button'; import {MdIconModule} from '../icon/index'; +import {MdStepIcon} from './step-icon'; +import {MdStepLabelContainer} from './step-label-container'; @NgModule({ imports: [ @@ -29,9 +31,9 @@ import {MdIconModule} from '../icon/index'; MdIconModule ], exports: [MdCommonModule, MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper, - MdStepperNext, MdStepperPrevious], + MdStepperNext, MdStepperPrevious, MdStepIcon, MdStepLabelContainer], declarations: [MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper, - MdStepperNext, MdStepperPrevious], + MdStepperNext, MdStepperPrevious, MdStepIcon, MdStepLabelContainer], }) export class MdStepperModule {} @@ -40,3 +42,5 @@ export * from './stepper-vertical'; export * from './step-label'; export * from './stepper'; export * from './stepper-button'; +export * from './step-icon'; +export * from './step-label-container'; diff --git a/src/lib/stepper/step-icon.html b/src/lib/stepper/step-icon.html new file mode 100644 index 000000000000..fb0c5029efbd --- /dev/null +++ b/src/lib/stepper/step-icon.html @@ -0,0 +1,5 @@ +
+ {{index + 1}} + create + done +
diff --git a/src/lib/stepper/step-icon.spec.ts b/src/lib/stepper/step-icon.spec.ts new file mode 100644 index 000000000000..261047352de4 --- /dev/null +++ b/src/lib/stepper/step-icon.spec.ts @@ -0,0 +1,83 @@ +import {MdStepperModule} from './index'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; +import {MdStep, MdStepper} from './stepper'; +import {MdStepIcon} from './step-icon'; +import {By} from '@angular/platform-browser'; + +describe('MdStepIcon', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdStepperModule], + declarations: [SimpleStepIconApp], + providers: [ + {provide: MdStepper, useClass: MdStepper} + ] + }); + TestBed.compileComponents(); + })); + + describe('setting icon', () => { + let stepIconComponent: MdStepIcon; + let fixture: ComponentFixture; + let testComponent: SimpleStepIconApp; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleStepIconApp); + fixture.detectChanges(); + + stepIconComponent = fixture.debugElement.query(By.css('md-step-icon')).componentInstance; + testComponent = fixture.componentInstance; + }); + + it('should set done icon if step is non-editable and completed', () => { + stepIconComponent.selected = true; + fixture.detectChanges(); + + expect(stepIconComponent._getIndicatorType()).toBe('number'); + + testComponent.mdStep.completed = true; + testComponent.mdStep.editable = false; + stepIconComponent.selected = false; + fixture.detectChanges(); + + expect(stepIconComponent._getIndicatorType()).toBe('done'); + }); + + it('should set create icon if step is editable and completed', () => { + stepIconComponent.selected = true; + fixture.detectChanges(); + + expect(stepIconComponent._getIndicatorType()).toBe('number'); + + testComponent.mdStep.completed = true; + testComponent.mdStep.editable = true; + stepIconComponent.selected = false; + fixture.detectChanges(); + + expect(stepIconComponent._getIndicatorType()).toBe('edit'); + }); + + it('should set "mat-step-icon-not-touched" class if the step ', () => { + let stepIconEl = fixture.debugElement.query(By.css('md-step-icon')).nativeElement; + + testComponent.mdStep.completed = false; + stepIconComponent.selected = false; + fixture.detectChanges(); + + expect(stepIconComponent._getIndicatorType()).toBe('number'); + expect(stepIconEl.classList).toContain('mat-step-icon-not-touched'); + }); + }); +}); + +@Component({ + template: ` + step + + ` +}) +class SimpleStepIconApp { + @ViewChild(MdStep) mdStep: MdStep; +} diff --git a/src/lib/stepper/step-icon.ts b/src/lib/stepper/step-icon.ts new file mode 100644 index 000000000000..db06752bbc04 --- /dev/null +++ b/src/lib/stepper/step-icon.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component} from '@angular/core'; +import {CdkStepIcon} from '@angular/cdk/stepper'; + +@Component({ + selector: 'md-step-icon, mat-step-icon', + templateUrl: 'step-icon.html', + host: { + 'class': 'mat-step-icon', + '[class.mat-step-icon-not-touched]': 'notTouched' + } +}) +export class MdStepIcon extends CdkStepIcon { } diff --git a/src/lib/stepper/step-label-container.html b/src/lib/stepper/step-label-container.html new file mode 100644 index 000000000000..51f933e84d52 --- /dev/null +++ b/src/lib/stepper/step-label-container.html @@ -0,0 +1,7 @@ + + + + +
{{step.label}}
+ +
Optional
\ No newline at end of file diff --git a/src/lib/stepper/step-label-container.ts b/src/lib/stepper/step-label-container.ts new file mode 100644 index 000000000000..2e38931cf14f --- /dev/null +++ b/src/lib/stepper/step-label-container.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component} from '@angular/core'; +import {CdkStepLabelContainer} from '@angular/cdk/stepper'; + +@Component({ + selector: 'md-step-label-container, mat-step-label-container', + templateUrl: 'step-label-container.html', + host: { + 'class': 'mat-step-label-container', + '[class.mat-step-label-active]': 'active', + '[class.mat-step-label-inactive]': '!active' + } +}) +export class MdStepLabelContainer extends CdkStepLabelContainer { } diff --git a/src/lib/stepper/stepper-horizontal.html b/src/lib/stepper/stepper-horizontal.html index 2108923513a1..640c4c1c4141 100644 --- a/src/lib/stepper/stepper-horizontal.html +++ b/src/lib/stepper/stepper-horizontal.html @@ -8,29 +8,14 @@ [tabIndex]="_focusIndex == i ? 0 : -1" (click)="step.select()" (keydown)="_onKeydown($event)"> -
- {{i + 1}} - create - done -
- -
- - - - -
{{step.label}}
- -
Optional
-
+ + +
-
+
-
- {{i + 1}} - create - done -
- -
- - - - -
{{step.label}}
- -
Optional
-
+ + +
diff --git a/src/lib/stepper/stepper.scss b/src/lib/stepper/stepper.scss index 6068ea634041..1262ebad1aea 100644 --- a/src/lib/stepper/stepper.scss +++ b/src/lib/stepper/stepper.scss @@ -13,8 +13,7 @@ $mat-step-optional-font-size: 12px; display: block; } -.mat-stepper-label-active, -.mat-stepper-label-inactive { +.mat-step-label-container { display: inline-block; white-space: nowrap; overflow: hidden; @@ -24,7 +23,7 @@ $mat-step-optional-font-size: 12px; vertical-align: middle; } -.mat-step-indicator { +.mat-step-icon { border-radius: 50%; height: $mat-stepper-label-header-height; width: $mat-stepper-label-header-height; @@ -47,7 +46,7 @@ $mat-step-optional-font-size: 12px; outline: none; padding: 0 $mat-stepper-side-gap; - .mat-step-indicator { + .mat-step-icon { margin-right: $mat-stepper-line-gap; flex: none; } @@ -60,7 +59,7 @@ $mat-step-optional-font-size: 12px; outline: none; max-height: $mat-stepper-label-header-height; - .mat-step-indicator { + .mat-step-icon { margin-right: $mat-vertical-stepper-content-margin - $mat-stepper-side-gap; } } diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index d96b723d555b..1cea4a39ad16 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -85,12 +85,6 @@ describe('MdHorizontalStepper', () => { it('should only be able to return to a previous step if it is editable', () => { assertEditableStepChange(stepperComponent, fixture); }); - - it('should show icon instead of index and active label on completed steps', () => { - let stepperComponentEl = fixture.debugElement - .query(By.css('md-horizontal-stepper')).nativeElement; - assertCorrectStepIcon(stepperComponent, stepperComponentEl, fixture); - }); }); describe('linear horizontal stepper', () => { @@ -209,12 +203,6 @@ describe('MdVerticalStepper', () => { it('should only be able to return to a previous step if it is editable', () => { assertEditableStepChange(stepperComponent, fixture); }); - - it('should show icon instead of index on completed steps', () => { - let stepperComponentEl = fixture.debugElement - .query(By.css('md-vertical-stepper')).nativeElement; - assertCorrectStepIcon(stepperComponent, stepperComponentEl, fixture); - }); }); describe('linear vertical stepper', () => { @@ -535,24 +523,6 @@ function assertOptionalStepValidity(stepperComponent: MdStepper, expect(stepperComponent.selectedIndex).toBe(1); } -/** Asserts that the correct step indicator is set upon step selection change event. */ -function assertCorrectStepIcon(stepperComponent: MdStepper, - stepperComponentEl: HTMLElement, - fixture: ComponentFixture) { - let stepsArray = stepperComponent._steps.toArray(); - expect(stepsArray[0].completed).toBe(false); - expect(stepperComponent._getIndicatorType(0)).toBe('number'); - expect(stepperComponentEl.querySelectorAll('.mat-stepper-label-active').length).toBe(1); - let nextButtonNativeEl = fixture.debugElement - .queryAll(By.directive(MdStepperNext))[0].nativeElement; - nextButtonNativeEl.click(); - fixture.detectChanges(); - - expect(stepsArray[0].completed).toBe(true); - expect(stepperComponent._getIndicatorType(0)).not.toBe('number'); - expect(stepperComponentEl.querySelectorAll('.mat-stepper-label-active').length).toBe(2); -} - @Component({ template: ` From a1f1977cebab3f28d695c5b5f90648d0658bdc1b Mon Sep 17 00:00:00 2001 From: Ji Won Shin Date: Mon, 21 Aug 2017 14:57:38 -0700 Subject: [PATCH 5/8] Refactor md-step-header and md-step-content + optional step change --- src/cdk/stepper/public_api.ts | 16 +-- src/cdk/stepper/step-content.ts | 61 ++++++++++ src/cdk/stepper/step-header.ts | 71 +++++++++++ src/cdk/stepper/step-icon.ts | 41 ------- src/cdk/stepper/step-label-container.ts | 28 ----- src/cdk/stepper/stepper.ts | 4 +- src/demo-app/stepper/stepper-demo.html | 22 ++-- src/demo-app/stepper/stepper-demo.ts | 12 +- src/lib/stepper/_stepper-theme.scss | 20 ++-- src/lib/stepper/index.ts | 12 +- src/lib/stepper/step-content.html | 3 + src/lib/stepper/step-content.scss | 6 + src/lib/stepper/step-content.ts | 30 +++++ src/lib/stepper/step-header.html | 20 ++++ src/lib/stepper/step-header.scss | 66 +++++++++++ src/lib/stepper/step-header.ts | 29 +++++ src/lib/stepper/step-icon.html | 5 - src/lib/stepper/step-icon.spec.ts | 83 ------------- src/lib/stepper/step-icon.ts | 20 ---- src/lib/stepper/step-label-container.html | 7 -- src/lib/stepper/step-label-container.ts | 21 ---- src/lib/stepper/stepper-horizontal.html | 34 +++--- src/lib/stepper/stepper-vertical.html | 34 +++--- src/lib/stepper/stepper.scss | 58 +-------- src/lib/stepper/stepper.spec.ts | 137 ++++++++++++++++++---- src/lib/stepper/stepper.ts | 4 +- 26 files changed, 476 insertions(+), 368 deletions(-) create mode 100644 src/cdk/stepper/step-content.ts create mode 100644 src/cdk/stepper/step-header.ts delete mode 100644 src/cdk/stepper/step-icon.ts delete mode 100644 src/cdk/stepper/step-label-container.ts create mode 100644 src/lib/stepper/step-content.html create mode 100644 src/lib/stepper/step-content.scss create mode 100644 src/lib/stepper/step-content.ts create mode 100644 src/lib/stepper/step-header.html create mode 100644 src/lib/stepper/step-header.scss create mode 100644 src/lib/stepper/step-header.ts delete mode 100644 src/lib/stepper/step-icon.html delete mode 100644 src/lib/stepper/step-icon.spec.ts delete mode 100644 src/lib/stepper/step-icon.ts delete mode 100644 src/lib/stepper/step-label-container.html delete mode 100644 src/lib/stepper/step-label-container.ts diff --git a/src/cdk/stepper/public_api.ts b/src/cdk/stepper/public_api.ts index 8989f3ee7df4..341d90ce7c89 100644 --- a/src/cdk/stepper/public_api.ts +++ b/src/cdk/stepper/public_api.ts @@ -11,20 +11,20 @@ import {CdkStepper, CdkStep} from './stepper'; import {CommonModule} from '@angular/common'; import {CdkStepLabel} from './step-label'; import {CdkStepperNext, CdkStepperPrevious} from './stepper-button'; -import {CdkStepIcon} from './step-icon'; -import {CdkStepLabelContainer} from './step-label-container'; +import {CdkStepHeader} from './step-header'; +import {CdkStepContent} from './step-content'; @NgModule({ imports: [CommonModule], - exports: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious, CdkStepIcon, - CdkStepLabelContainer], - declarations: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious, CdkStepIcon, - CdkStepLabelContainer] + exports: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious, CdkStepHeader, + CdkStepContent], + declarations: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious, + CdkStepHeader, CdkStepContent] }) export class CdkStepperModule {} export * from './stepper'; export * from './step-label'; export * from './stepper-button'; -export * from './step-icon'; -export * from './step-label-container'; +export * from './step-header'; +export * from './step-content'; diff --git a/src/cdk/stepper/step-content.ts b/src/cdk/stepper/step-content.ts new file mode 100644 index 000000000000..a399da31ea31 --- /dev/null +++ b/src/cdk/stepper/step-content.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive, Input} from '@angular/core'; +import {CdkStep, CdkStepper} from './stepper'; +import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion'; + +@Directive({ + selector: 'cdkStepContent', + host: { + 'role': 'tabpanel', + '[attr.id]': 'contentId', + '[attr.aria-labelledby]': 'labelId', + '[attr.aria-expanded]': 'selectedIndex == index', + } +}) +export class CdkStepContent { + /** Whether the orientation of stepper is horizontal. */ + @Input() + get horizontal() { return this._horizontal; } + set horizontal(value: any) { + this._horizontal = coerceBooleanProperty(value); + } + private _horizontal: boolean; + + /** Unique label ID of step header. */ + @Input() + labelId: string; + + /** Unique content ID of step content. */ + @Input() + contentId: string; + + /** Index of the given step. */ + @Input() + get index() { return this._index; } + set index(value: any) { + this._index = coerceNumberProperty(value); + } + private _index: number; + + /** Index of selected step in stepper. */ + @Input() + get selectedIndex() { return this._selectedIndex; } + set selectedIndex(value: any) { + this._selectedIndex = coerceNumberProperty(value); + } + private _selectedIndex: number; + + /** Returns the step at the index position in stepper. */ + get step(): CdkStep { + return this._stepper._steps.toArray()[this._index]; + } + + constructor(private _stepper: CdkStepper) { } +} diff --git a/src/cdk/stepper/step-header.ts b/src/cdk/stepper/step-header.ts new file mode 100644 index 000000000000..9d90b6aef62c --- /dev/null +++ b/src/cdk/stepper/step-header.ts @@ -0,0 +1,71 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive, Input} from '@angular/core'; +import {CdkStep, CdkStepper} from './stepper'; +import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion'; + +@Directive({ + selector: 'cdkStepHeader', + host: { + 'role': 'tab', + '[attr.id]': 'labelId', + '[attr.aria-controls]': 'contentId', + '[attr.aria-selected]': 'selected' + } +}) +export class CdkStepHeader { + /** Whether the orientation of stepper is horizontal. */ + @Input() + get horizontal() { return this._horizontal; } + set horizontal(value: any) { + this._horizontal = coerceBooleanProperty(value); + } + private _horizontal: boolean; + + /** Unique label ID of step header. */ + @Input() + labelId: string; + + /** Unique content ID of step content. */ + @Input() + contentId: string; + + /** Index of the given step. */ + @Input() + get index() { return this._index; } + set index(value: any) { + this._index = coerceNumberProperty(value); + } + private _index: number; + + /** Whether the given step is selected. */ + @Input() + get selected() { return this._selected; } + set selected(value: any) { + this._selected = coerceBooleanProperty(value); + } + private _selected: boolean; + + + /** Returns the step at the index position in stepper. */ + get step(): CdkStep { + return this._stepper._steps.toArray()[this._index]; + } + + constructor(private _stepper: CdkStepper) { } + + /** Returns the type of icon to be displayed. */ + _getIndicatorType(): 'number' | 'edit' | 'done' { + if (!this.step.completed || this._selected) { + return 'number'; + } else { + return this.step.editable ? 'edit' : 'done'; + } + } +} diff --git a/src/cdk/stepper/step-icon.ts b/src/cdk/stepper/step-icon.ts deleted file mode 100644 index 7fb2ba3e2d66..000000000000 --- a/src/cdk/stepper/step-icon.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Directive, Input} from '@angular/core'; -import {CdkStep} from './stepper'; - -@Directive({ - selector: 'cdkStepIcon' -}) -export class CdkStepIcon { - /** Step of the icon to be displayed. */ - @Input() - step: CdkStep; - - /** Whether the step of the icon to be displayed is active. */ - @Input() - selected: boolean; - - /** Index of the step. */ - @Input() - index: number; - - /** Whether the user has touched the step that is not selected. */ - get notTouched() { - return this._getIndicatorType() == 'number' && !this.selected; - } - - /** Returns the type of icon to be displayed. */ - _getIndicatorType(): 'number' | 'edit' | 'done' { - if (!this.step.completed || this.selected) { - return 'number'; - } else { - return this.step.editable ? 'edit' : 'done'; - } - } -} diff --git a/src/cdk/stepper/step-label-container.ts b/src/cdk/stepper/step-label-container.ts deleted file mode 100644 index cec0f4320dee..000000000000 --- a/src/cdk/stepper/step-label-container.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Directive, Input} from '@angular/core'; -import {CdkStep} from './stepper'; - -@Directive({ - selector: 'cdkStepLabelContainer' -}) -export class CdkStepLabelContainer { - /** Step of the label to be displayed. */ - @Input() - step: CdkStep; - - /** Whether the step of label to be displayed is selected. */ - @Input() - selected: boolean; - - /** Whether the label to be displayed is active. */ - get active() { - return this.step.completed || this.selected; - } -} diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index 02b47b0ad2cb..5b5751a1fbcf 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -164,7 +164,7 @@ export class CdkStepper { _focusIndex: number = 0; /** Used to track unique ID for each stepper component. */ - private _groupId: number; + _groupId: number; constructor() { this._groupId = nextId++; @@ -241,7 +241,7 @@ export class CdkStepper { const stepsArray = this._steps.toArray(); stepsArray[this._selectedIndex].interacted = true; if (this._linear) { - return stepsArray.slice(0, index).some(step => step.stepControl.invalid && !step.optional); + return stepsArray.slice(0, index).some(step => step.stepControl.invalid); } return false; } diff --git a/src/demo-app/stepper/stepper-demo.html b/src/demo-app/stepper/stepper-demo.html index aae8e205710f..dbbc6b501ed1 100644 --- a/src/demo-app/stepper/stepper-demo.html +++ b/src/demo-app/stepper/stepper-demo.html @@ -21,11 +21,11 @@

Linear Vertical Stepper Demo using a single form

-
Fill out your phone number
+
Fill out your email address
- - This field is required + + The input is invalid.
@@ -62,12 +62,12 @@

Linear Horizontal Stepper Demo using a different form for each step

- -
+ + Fill out your phone number - - This field is required + + The input is invalid
@@ -156,9 +156,7 @@

Horizontal Stepper Demo

- -
Fill out your phone number
-
+ Fill out your phone number @@ -169,9 +167,7 @@

Horizontal Stepper Demo

- -
Fill out your address
-
+ Fill out your address diff --git a/src/demo-app/stepper/stepper-demo.ts b/src/demo-app/stepper/stepper-demo.ts index d220a99273a2..2c7ed6f7ce92 100644 --- a/src/demo-app/stepper/stepper-demo.ts +++ b/src/demo-app/stepper/stepper-demo.ts @@ -1,6 +1,8 @@ import {Component} from '@angular/core'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; + @Component({ moduleId: module.id, selector: 'stepper-demo', @@ -13,7 +15,7 @@ export class StepperDemo { isNonEditable = false; nameFormGroup: FormGroup; - phoneFormGroup: FormGroup; + emailFormGroup: FormGroup; steps = [ {label: 'Confirm your name', content: 'Last name, First name.'}, @@ -35,8 +37,8 @@ export class StepperDemo { lastNameFormCtrl: ['', Validators.required], }), this._formBuilder.group({ - phoneFormCtrl: ['', Validators.required], - }) + emailFormCtrl: ['', Validators.pattern(EMAIL_REGEX)] + }), ]) }); @@ -45,8 +47,8 @@ export class StepperDemo { lastNameCtrl: ['', Validators.required], }); - this.phoneFormGroup = this._formBuilder.group({ - phoneCtrl: ['', Validators.required] + this.emailFormGroup = this._formBuilder.group({ + emailCtrl: ['', Validators.pattern(EMAIL_REGEX)] }); } } diff --git a/src/lib/stepper/_stepper-theme.scss b/src/lib/stepper/_stepper-theme.scss index a2cd4cfb23b3..b7f0467251e1 100644 --- a/src/lib/stepper/_stepper-theme.scss +++ b/src/lib/stepper/_stepper-theme.scss @@ -7,18 +7,19 @@ $background: map-get($theme, background); $primary: map-get($theme, primary); - .mat-horizontal-stepper-header, .mat-vertical-stepper-header { - + .mat-stepper-header { &:focus, &:hover { background-color: mat-color($background, hover); } + } + .mat-step-header { - .mat-stepper-label-active { + .mat-step-label-active { color: mat-color($foreground, text); } - .mat-stepper-label-inactive, + .mat-step-label-inactive, .mat-step-optional { color: mat-color($foreground, disabled-text); } @@ -28,14 +29,9 @@ color: mat-color($primary, default-contrast); } - &[aria-selected='false'] { - .mat-stepper-label { - color: mat-color($foreground, disabled-text); - } - - .mat-step-icon-not-touched { - background-color: mat-color($foreground, disabled-text); - } + .mat-step-icon-not-touched { + background-color: mat-color($foreground, disabled-text); + color: mat-color($primary, default-contrast); } } diff --git a/src/lib/stepper/index.ts b/src/lib/stepper/index.ts index ebfbd989a2b6..b5dbb19d1332 100644 --- a/src/lib/stepper/index.ts +++ b/src/lib/stepper/index.ts @@ -18,8 +18,8 @@ import {MdCommonModule} from '../core'; import {MdStepLabel} from './step-label'; import {MdStepperNext, MdStepperPrevious} from './stepper-button'; import {MdIconModule} from '../icon/index'; -import {MdStepIcon} from './step-icon'; -import {MdStepLabelContainer} from './step-label-container'; +import {MdStepHeader} from './step-header'; +import {MdStepContent} from './step-content'; @NgModule({ imports: [ @@ -31,9 +31,9 @@ import {MdStepLabelContainer} from './step-label-container'; MdIconModule ], exports: [MdCommonModule, MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper, - MdStepperNext, MdStepperPrevious, MdStepIcon, MdStepLabelContainer], + MdStepperNext, MdStepperPrevious, MdStepHeader, MdStepContent], declarations: [MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper, - MdStepperNext, MdStepperPrevious, MdStepIcon, MdStepLabelContainer], + MdStepperNext, MdStepperPrevious, MdStepHeader, MdStepContent], }) export class MdStepperModule {} @@ -42,5 +42,5 @@ export * from './stepper-vertical'; export * from './step-label'; export * from './stepper'; export * from './stepper-button'; -export * from './step-icon'; -export * from './step-label-container'; +export * from './step-header'; +export * from './step-content'; diff --git a/src/lib/stepper/step-content.html b/src/lib/stepper/step-content.html new file mode 100644 index 000000000000..93615940c98e --- /dev/null +++ b/src/lib/stepper/step-content.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/lib/stepper/step-content.scss b/src/lib/stepper/step-content.scss new file mode 100644 index 000000000000..784ea496d427 --- /dev/null +++ b/src/lib/stepper/step-content.scss @@ -0,0 +1,6 @@ +$mat-stepper-side-gap: 24px !default; + +.mat-vertical-content { + padding: 0 $mat-stepper-side-gap $mat-stepper-side-gap $mat-stepper-side-gap; + overflow: hidden; +} diff --git a/src/lib/stepper/step-content.ts b/src/lib/stepper/step-content.ts new file mode 100644 index 000000000000..8fdd34ab9dc1 --- /dev/null +++ b/src/lib/stepper/step-content.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component} from '@angular/core'; +import {CdkStepContent} from '@angular/cdk/stepper'; +import {MdStepper} from './stepper'; + +@Component({ + selector: 'md-step-content, mat-step-content', + templateUrl: 'step-content.html', + styleUrls: ['step-content.css'], + host: { + '[class.mat-vertical-stepper-content]': '!horizontal', + '[class.mat-horizontal-stepper-content]': 'horizontal', + 'role': 'tabpanel', + '[attr.id]': 'contentId', + '[attr.aria-labelledby]': 'labelId', + '[attr.aria-expanded]': 'selectedIndex == index', + }, +}) +export class MdStepContent extends CdkStepContent { + constructor(mdStepper: MdStepper) { + super(mdStepper); + } +} diff --git a/src/lib/stepper/step-header.html b/src/lib/stepper/step-header.html new file mode 100644 index 000000000000..fa8a7d353a85 --- /dev/null +++ b/src/lib/stepper/step-header.html @@ -0,0 +1,20 @@ +
+
+ {{index + 1}} + create + done +
+
+ + + + +
{{step.label}}
+ +
Optional
+
+
diff --git a/src/lib/stepper/step-header.scss b/src/lib/stepper/step-header.scss new file mode 100644 index 000000000000..5ac7201681bd --- /dev/null +++ b/src/lib/stepper/step-header.scss @@ -0,0 +1,66 @@ +$mat-horizontal-stepper-header-height: 72px !default; +$mat-stepper-label-header-height: 24px !default; +$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; + +:host { + display: flex; +} + +.mat-horizontal-stepper-header { + display: flex; + height: $mat-horizontal-stepper-header-height; + overflow: hidden; + align-items: center; + padding: 0 $mat-stepper-side-gap; + + .mat-step-icon, + .mat-step-icon-not-touched { + margin-right: $mat-stepper-line-gap; + flex: none; + } +} + +.mat-vertical-stepper-header { + display: flex; + align-items: center; + padding: $mat-stepper-side-gap; + max-height: $mat-stepper-label-header-height; + + .mat-step-icon, + .mat-step-icon-not-touched { + margin-right: $mat-vertical-stepper-content-margin - $mat-stepper-side-gap; + } +} + +.mat-step-optional { + font-size: $mat-step-optional-font-size; +} + +.mat-step-icon, +.mat-step-icon-not-touched { + border-radius: 50%; + height: $mat-stepper-label-header-height; + width: $mat-stepper-label-header-height; + text-align: center; + line-height: $mat-stepper-label-header-height; + display: inline-block; +} + +.mat-step-label-active, +.mat-step-label-inactive { + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: $mat-stepper-label-min-width; + vertical-align: middle; +} + +.text-label { + text-overflow: ellipsis; + overflow: hidden; +} diff --git a/src/lib/stepper/step-header.ts b/src/lib/stepper/step-header.ts new file mode 100644 index 000000000000..d7612c6ccf68 --- /dev/null +++ b/src/lib/stepper/step-header.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component} from '@angular/core'; +import {CdkStepHeader} from '@angular/cdk/stepper'; +import {MdStepper} from './stepper'; + +@Component({ + selector: 'md-step-header, mat-step-header', + templateUrl: 'step-header.html', + styleUrls: ['step-header.css'], + host: { + 'class': 'mat-step-header', + 'role': 'tab', + '[attr.id]': 'labelId', + '[attr.aria-controls]': 'contentId', + '[attr.aria-selected]': 'selected' + } +}) +export class MdStepHeader extends CdkStepHeader { + constructor(mdStepper: MdStepper) { + super(mdStepper); + } +} diff --git a/src/lib/stepper/step-icon.html b/src/lib/stepper/step-icon.html deleted file mode 100644 index fb0c5029efbd..000000000000 --- a/src/lib/stepper/step-icon.html +++ /dev/null @@ -1,5 +0,0 @@ -
- {{index + 1}} - create - done -
diff --git a/src/lib/stepper/step-icon.spec.ts b/src/lib/stepper/step-icon.spec.ts deleted file mode 100644 index 261047352de4..000000000000 --- a/src/lib/stepper/step-icon.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import {MdStepperModule} from './index'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {Component, ViewChild} from '@angular/core'; -import {MdStep, MdStepper} from './stepper'; -import {MdStepIcon} from './step-icon'; -import {By} from '@angular/platform-browser'; - -describe('MdStepIcon', () => { - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [MdStepperModule], - declarations: [SimpleStepIconApp], - providers: [ - {provide: MdStepper, useClass: MdStepper} - ] - }); - TestBed.compileComponents(); - })); - - describe('setting icon', () => { - let stepIconComponent: MdStepIcon; - let fixture: ComponentFixture; - let testComponent: SimpleStepIconApp; - - beforeEach(() => { - fixture = TestBed.createComponent(SimpleStepIconApp); - fixture.detectChanges(); - - stepIconComponent = fixture.debugElement.query(By.css('md-step-icon')).componentInstance; - testComponent = fixture.componentInstance; - }); - - it('should set done icon if step is non-editable and completed', () => { - stepIconComponent.selected = true; - fixture.detectChanges(); - - expect(stepIconComponent._getIndicatorType()).toBe('number'); - - testComponent.mdStep.completed = true; - testComponent.mdStep.editable = false; - stepIconComponent.selected = false; - fixture.detectChanges(); - - expect(stepIconComponent._getIndicatorType()).toBe('done'); - }); - - it('should set create icon if step is editable and completed', () => { - stepIconComponent.selected = true; - fixture.detectChanges(); - - expect(stepIconComponent._getIndicatorType()).toBe('number'); - - testComponent.mdStep.completed = true; - testComponent.mdStep.editable = true; - stepIconComponent.selected = false; - fixture.detectChanges(); - - expect(stepIconComponent._getIndicatorType()).toBe('edit'); - }); - - it('should set "mat-step-icon-not-touched" class if the step ', () => { - let stepIconEl = fixture.debugElement.query(By.css('md-step-icon')).nativeElement; - - testComponent.mdStep.completed = false; - stepIconComponent.selected = false; - fixture.detectChanges(); - - expect(stepIconComponent._getIndicatorType()).toBe('number'); - expect(stepIconEl.classList).toContain('mat-step-icon-not-touched'); - }); - }); -}); - -@Component({ - template: ` - step - - ` -}) -class SimpleStepIconApp { - @ViewChild(MdStep) mdStep: MdStep; -} diff --git a/src/lib/stepper/step-icon.ts b/src/lib/stepper/step-icon.ts deleted file mode 100644 index db06752bbc04..000000000000 --- a/src/lib/stepper/step-icon.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Component} from '@angular/core'; -import {CdkStepIcon} from '@angular/cdk/stepper'; - -@Component({ - selector: 'md-step-icon, mat-step-icon', - templateUrl: 'step-icon.html', - host: { - 'class': 'mat-step-icon', - '[class.mat-step-icon-not-touched]': 'notTouched' - } -}) -export class MdStepIcon extends CdkStepIcon { } diff --git a/src/lib/stepper/step-label-container.html b/src/lib/stepper/step-label-container.html deleted file mode 100644 index 51f933e84d52..000000000000 --- a/src/lib/stepper/step-label-container.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - -
{{step.label}}
- -
Optional
\ No newline at end of file diff --git a/src/lib/stepper/step-label-container.ts b/src/lib/stepper/step-label-container.ts deleted file mode 100644 index 2e38931cf14f..000000000000 --- a/src/lib/stepper/step-label-container.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Component} from '@angular/core'; -import {CdkStepLabelContainer} from '@angular/cdk/stepper'; - -@Component({ - selector: 'md-step-label-container, mat-step-label-container', - templateUrl: 'step-label-container.html', - host: { - 'class': 'mat-step-label-container', - '[class.mat-step-label-active]': 'active', - '[class.mat-step-label-inactive]': '!active' - } -}) -export class MdStepLabelContainer extends CdkStepLabelContainer { } diff --git a/src/lib/stepper/stepper-horizontal.html b/src/lib/stepper/stepper-horizontal.html index 640c4c1c4141..6f8c0e7b40f8 100644 --- a/src/lib/stepper/stepper-horizontal.html +++ b/src/lib/stepper/stepper-horizontal.html @@ -1,28 +1,24 @@
-
-
- -
+
diff --git a/src/lib/stepper/stepper-vertical.html b/src/lib/stepper/stepper-vertical.html index 40e2d4f49fb1..53e84e8ac1a6 100644 --- a/src/lib/stepper/stepper-vertical.html +++ b/src/lib/stepper/stepper-vertical.html @@ -1,25 +1,21 @@
- diff --git a/src/lib/stepper/stepper.scss b/src/lib/stepper/stepper.scss index 1262ebad1aea..cf13ec8f9234 100644 --- a/src/lib/stepper/stepper.scss +++ b/src/lib/stepper/stepper.scss @@ -1,35 +1,17 @@ @import '../core/style/variables'; -$mat-horizontal-stepper-header-height: 72px !default; -$mat-stepper-label-header-height: 24px !default; -$mat-stepper-label-min-width: 50px !default; $mat-stepper-side-gap: 24px !default; $mat-vertical-stepper-content-margin: 36px !default; $mat-stepper-line-width: 1px !default; $mat-stepper-line-gap: 8px !default; -$mat-step-optional-font-size: 12px; :host { display: block; } -.mat-step-label-container { - display: inline-block; - white-space: nowrap; +.mat-stepper-header { overflow: hidden; - // TODO(jwshin): text-overflow does not work as expected. - text-overflow: ellipsis; - min-width: $mat-stepper-label-min-width; - vertical-align: middle; -} - -.mat-step-icon { - border-radius: 50%; - height: $mat-stepper-label-header-height; - width: $mat-stepper-label-header-height; - text-align: center; - line-height: $mat-stepper-label-header-height; - display: inline-block; + outline: none; } .mat-horizontal-stepper-header-container { @@ -38,36 +20,6 @@ $mat-step-optional-font-size: 12px; align-items: center; } -.mat-horizontal-stepper-header { - display: flex; - height: $mat-horizontal-stepper-header-height; - overflow: hidden; - align-items: center; - outline: none; - padding: 0 $mat-stepper-side-gap; - - .mat-step-icon { - margin-right: $mat-stepper-line-gap; - flex: none; - } -} - -.mat-vertical-stepper-header { - display: flex; - align-items: center; - padding: $mat-stepper-side-gap; - outline: none; - max-height: $mat-stepper-label-header-height; - - .mat-step-icon { - margin-right: $mat-vertical-stepper-content-margin - $mat-stepper-side-gap; - } -} - -.mat-step-optional { - font-size: $mat-step-optional-font-size; -} - .mat-stepper-horizontal-line { border-top-width: $mat-stepper-line-width; border-top-style: solid; @@ -79,6 +31,7 @@ $mat-step-optional-font-size: 12px; .mat-horizontal-stepper-content { overflow: hidden; + display: flex; &[aria-expanded='false'] { height: 0; @@ -107,13 +60,10 @@ $mat-step-optional-font-size: 12px; } .mat-vertical-stepper-content { + display: flex; overflow: hidden; } -.mat-vertical-content { - padding: 0 $mat-stepper-side-gap $mat-stepper-side-gap $mat-stepper-side-gap; -} - .mat-step:last-child { .mat-vertical-content-container { border: none; diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index 1cea4a39ad16..333a8d06016f 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -11,6 +11,8 @@ import {dispatchKeyboardEvent} from '@angular/cdk/testing'; import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes'; import {MdStepper} from './stepper'; +const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; + describe('MdHorizontalStepper', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -77,14 +79,20 @@ describe('MdHorizontalStepper', () => { }); it('should not set focus on header of selected step if header is not clicked', () => { - let stepHeaderEl = fixture.debugElement - .queryAll(By.css('.mat-horizontal-stepper-header'))[1].nativeElement; - assertStepHeaderFocusNotCalled(stepHeaderEl, stepperComponent, fixture); + assertStepHeaderFocusNotCalled(stepperComponent, fixture); }); it('should only be able to return to a previous step if it is editable', () => { assertEditableStepChange(stepperComponent, fixture); }); + + it('should set create icon if step is editable and completed', () => { + assertCorrectStepIcon(stepperComponent, fixture, true, 'edit'); + }); + + it('should set done icon if step is not editable and is completed', () => { + assertCorrectStepIcon(stepperComponent, fixture, false, 'done'); + }); }); describe('linear horizontal stepper', () => { @@ -117,9 +125,7 @@ describe('MdHorizontalStepper', () => { }); it('should not focus step header upon click if it is not able to be selected', () => { - let stepHeaderEl = fixture.debugElement - .queryAll(By.css('.mat-horizontal-stepper-header'))[1].nativeElement; - assertStepHeaderBlurred(stepHeaderEl, fixture); + assertStepHeaderBlurred(fixture); }); it('should be able to move to next step even when invalid if current step is optional', () => { @@ -195,14 +201,20 @@ describe('MdVerticalStepper', () => { }); it('should not set focus on header of selected step if header is not clicked', () => { - let stepHeaderEl = fixture.debugElement - .queryAll(By.css('.mat-vertical-stepper-header'))[1].nativeElement; - assertStepHeaderFocusNotCalled(stepHeaderEl, stepperComponent, fixture); + assertStepHeaderFocusNotCalled(stepperComponent, fixture); }); it('should only be able to return to a previous step if it is editable', () => { assertEditableStepChange(stepperComponent, fixture); }); + + it('should set create icon if step is editable and completed', () => { + assertCorrectStepIcon(stepperComponent, fixture, true, 'edit'); + }); + + it('should set done icon if step is not editable and is completed', () => { + assertCorrectStepIcon(stepperComponent, fixture, false, 'done'); + }); }); describe('linear vertical stepper', () => { @@ -236,9 +248,7 @@ describe('MdVerticalStepper', () => { }); it('should not focus step header upon click if it is not able to be selected', () => { - let stepHeaderEl = fixture.debugElement - .queryAll(By.css('.mat-vertical-stepper-header'))[1].nativeElement; - assertStepHeaderBlurred(stepHeaderEl, fixture); + assertStepHeaderBlurred(fixture); }); it('should be able to move to next step even when invalid if current step is optional', () => { @@ -436,9 +446,10 @@ function assertCorrectKeyboardInteraction(stepperComponent: MdStepper, } /** Asserts that step selection change using stepper buttons does not focus step header. */ -function assertStepHeaderFocusNotCalled(stepHeaderEl: HTMLElement, - stepperComponent: MdStepper, - fixture: ComponentFixture) { +function assertStepHeaderFocusNotCalled(stepperComponent: MdStepper, + fixture: ComponentFixture) { + let stepHeaderEl = fixture.debugElement + .queryAll(By.css('.mat-stepper-header'))[1].nativeElement; let nextButtonNativeEl = fixture.debugElement .queryAll(By.directive(MdStepperNext))[0].nativeElement; spyOn(stepHeaderEl, 'focus'); @@ -478,7 +489,9 @@ function assertLinearStepperValidity(stepHeaderEl: HTMLElement, } /** Asserts that step header focus is blurred if the step cannot be selected upon header click. */ -function assertStepHeaderBlurred(stepHeaderEl: HTMLElement, fixture: ComponentFixture) { +function assertStepHeaderBlurred(fixture: ComponentFixture) { + let stepHeaderEl = fixture.debugElement + .queryAll(By.css('.mat-stepper-header'))[1].nativeElement; spyOn(stepHeaderEl, 'blur'); stepHeaderEl.click(); fixture.detectChanges(); @@ -505,22 +518,62 @@ function assertEditableStepChange(stepperComponent: MdStepper, expect(stepperComponent.selectedIndex).toBe(0); } -/** Asserts that it is only possible to skip a step in linear stepper if the step is optional. */ +/** + * Asserts that it is possible to skip an optional step in linear stepper if there is no input + * or the input is valid. + */ function assertOptionalStepValidity(stepperComponent: MdStepper, testComponent: LinearMdHorizontalStepperApp | LinearMdVerticalStepperApp, fixture: ComponentFixture) { - expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe(''); - expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false); - expect(testComponent.oneGroup.valid).toBe(false); - expect(stepperComponent.selectedIndex).toBe(0); + testComponent.oneGroup.get('oneCtrl')!.setValue('input'); + testComponent.twoGroup.get('twoCtrl')!.setValue('input'); + stepperComponent.selectedIndex = 2; + fixture.detectChanges(); + + expect(stepperComponent.selectedIndex).toBe(2); + expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(true); + + let nextButtonNativeEl = fixture.debugElement + .queryAll(By.directive(MdStepperNext))[2].nativeElement; + nextButtonNativeEl.click(); + fixture.detectChanges(); + + expect(stepperComponent.selectedIndex) + .toBe(3, 'Expected selectedIndex to change when optional step input is empty.'); + + stepperComponent.selectedIndex = 2; + testComponent.threeGroup.get('threeCtrl')!.setValue('input'); + nextButtonNativeEl.click(); + fixture.detectChanges(); + + expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(false); + expect(stepperComponent.selectedIndex) + .toBe(2, 'Expected selectedIndex to remain unchanged when optional step input is invalid.'); + + testComponent.threeGroup.get('threeCtrl')!.setValue('123@gmail.com'); + nextButtonNativeEl.click(); + fixture.detectChanges(); + + expect(testComponent.threeGroup.get('threeCtrl')!.valid).toBe(true); + expect(stepperComponent.selectedIndex) + .toBe(3, 'Expected selectedIndex to change when optional step input is valid.'); +} - stepperComponent._steps.toArray()[0].optional = true; +/** Asserts that step header set the correct icon depending on the state of step. */ +function assertCorrectStepIcon(stepperComponent: MdStepper, + fixture: ComponentFixture, + isEditable: boolean, + icon: String) { + let stepHeaderComponent = fixture.debugElement + .queryAll(By.css('md-step-header'))[0].componentInstance; let nextButtonNativeEl = fixture.debugElement .queryAll(By.directive(MdStepperNext))[0].nativeElement; + expect(stepHeaderComponent._getIndicatorType()).toBe('number'); + stepperComponent._steps.toArray()[0].editable = isEditable; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepperComponent.selectedIndex).toBe(1); + expect(stepHeaderComponent._getIndicatorType()).toBe(icon); } @Component({ @@ -583,12 +636,28 @@ class SimpleMdHorizontalStepperApp {
+ +
+ Step two + + + +
+ + +
+
+
+ + Done + ` }) class LinearMdHorizontalStepperApp { oneGroup: FormGroup; twoGroup: FormGroup; + threeGroup: FormGroup; ngOnInit() { this.oneGroup = new FormGroup({ @@ -597,6 +666,9 @@ class LinearMdHorizontalStepperApp { this.twoGroup = new FormGroup({ twoCtrl: new FormControl('', Validators.required) }); + this.threeGroup = new FormGroup({ + threeCtrl: new FormControl('', Validators.pattern(EMAIL_REGEX)) + }); } } @@ -660,12 +732,28 @@ class SimpleMdVerticalStepperApp {
+ +
+ Step two + + + +
+ + +
+
+
+ + Done + ` }) class LinearMdVerticalStepperApp { oneGroup: FormGroup; twoGroup: FormGroup; + threeGroup: FormGroup; ngOnInit() { this.oneGroup = new FormGroup({ @@ -674,5 +762,8 @@ class LinearMdVerticalStepperApp { this.twoGroup = new FormGroup({ twoCtrl: new FormControl('', Validators.required) }); + this.threeGroup = new FormGroup({ + threeCtrl: new FormControl('', Validators.pattern(EMAIL_REGEX)) + }); } } diff --git a/src/lib/stepper/stepper.ts b/src/lib/stepper/stepper.ts index 5a0dd890fa89..a68241050e8d 100644 --- a/src/lib/stepper/stepper.ts +++ b/src/lib/stepper/stepper.ts @@ -20,7 +20,7 @@ import { QueryList, SkipSelf, ViewChildren -}from '@angular/core'; +} from '@angular/core'; import {MdStepLabel} from './step-label'; import { defaultErrorStateMatcher, @@ -66,7 +66,7 @@ export class MdStep extends CdkStep implements ErrorOptions { export class MdStepper extends CdkStepper implements ErrorOptions { /** The list of step headers of the steps in the stepper. */ - @ViewChildren('stepHeader') _stepHeader: QueryList; + @ViewChildren('stepperHeader') _stepHeader: QueryList; /** Steps that the stepper holds. */ @ContentChildren(MdStep) _steps: QueryList; From ec3993a4edf0ba29fc38eeca63f2402cc6b4cca6 Mon Sep 17 00:00:00 2001 From: Ji Won Shin Date: Mon, 21 Aug 2017 18:06:47 -0700 Subject: [PATCH 6/8] Simplify code based on review --- src/cdk/stepper/public_api.ts | 10 +--- src/cdk/stepper/step-content.ts | 61 --------------------- src/cdk/stepper/step-header.ts | 71 ------------------------- src/cdk/stepper/stepper.ts | 20 +++++-- src/lib/stepper/_stepper-theme.scss | 2 +- src/lib/stepper/index.ts | 6 +-- src/lib/stepper/step-content.html | 3 -- src/lib/stepper/step-content.scss | 6 --- src/lib/stepper/step-content.ts | 30 ----------- src/lib/stepper/step-header.html | 33 ++++++------ src/lib/stepper/step-header.scss | 29 +--------- src/lib/stepper/step-header.ts | 71 ++++++++++++++++++++++--- src/lib/stepper/stepper-horizontal.html | 24 +++++---- src/lib/stepper/stepper-horizontal.ts | 5 +- src/lib/stepper/stepper-vertical.html | 25 +++++---- src/lib/stepper/stepper-vertical.ts | 5 +- src/lib/stepper/stepper.scss | 34 +++++++++++- src/lib/stepper/stepper.spec.ts | 6 +-- src/lib/stepper/stepper.ts | 5 +- 19 files changed, 175 insertions(+), 271 deletions(-) delete mode 100644 src/cdk/stepper/step-content.ts delete mode 100644 src/cdk/stepper/step-header.ts delete mode 100644 src/lib/stepper/step-content.html delete mode 100644 src/lib/stepper/step-content.scss delete mode 100644 src/lib/stepper/step-content.ts diff --git a/src/cdk/stepper/public_api.ts b/src/cdk/stepper/public_api.ts index 341d90ce7c89..cfb2fec4dc26 100644 --- a/src/cdk/stepper/public_api.ts +++ b/src/cdk/stepper/public_api.ts @@ -11,20 +11,14 @@ import {CdkStepper, CdkStep} from './stepper'; import {CommonModule} from '@angular/common'; import {CdkStepLabel} from './step-label'; import {CdkStepperNext, CdkStepperPrevious} from './stepper-button'; -import {CdkStepHeader} from './step-header'; -import {CdkStepContent} from './step-content'; @NgModule({ imports: [CommonModule], - exports: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious, CdkStepHeader, - CdkStepContent], - declarations: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious, - CdkStepHeader, CdkStepContent] + exports: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious], + declarations: [CdkStep, CdkStepper, CdkStepLabel, CdkStepperNext, CdkStepperPrevious] }) export class CdkStepperModule {} export * from './stepper'; export * from './step-label'; export * from './stepper-button'; -export * from './step-header'; -export * from './step-content'; diff --git a/src/cdk/stepper/step-content.ts b/src/cdk/stepper/step-content.ts deleted file mode 100644 index a399da31ea31..000000000000 --- a/src/cdk/stepper/step-content.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Directive, Input} from '@angular/core'; -import {CdkStep, CdkStepper} from './stepper'; -import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion'; - -@Directive({ - selector: 'cdkStepContent', - host: { - 'role': 'tabpanel', - '[attr.id]': 'contentId', - '[attr.aria-labelledby]': 'labelId', - '[attr.aria-expanded]': 'selectedIndex == index', - } -}) -export class CdkStepContent { - /** Whether the orientation of stepper is horizontal. */ - @Input() - get horizontal() { return this._horizontal; } - set horizontal(value: any) { - this._horizontal = coerceBooleanProperty(value); - } - private _horizontal: boolean; - - /** Unique label ID of step header. */ - @Input() - labelId: string; - - /** Unique content ID of step content. */ - @Input() - contentId: string; - - /** Index of the given step. */ - @Input() - get index() { return this._index; } - set index(value: any) { - this._index = coerceNumberProperty(value); - } - private _index: number; - - /** Index of selected step in stepper. */ - @Input() - get selectedIndex() { return this._selectedIndex; } - set selectedIndex(value: any) { - this._selectedIndex = coerceNumberProperty(value); - } - private _selectedIndex: number; - - /** Returns the step at the index position in stepper. */ - get step(): CdkStep { - return this._stepper._steps.toArray()[this._index]; - } - - constructor(private _stepper: CdkStepper) { } -} diff --git a/src/cdk/stepper/step-header.ts b/src/cdk/stepper/step-header.ts deleted file mode 100644 index 9d90b6aef62c..000000000000 --- a/src/cdk/stepper/step-header.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Directive, Input} from '@angular/core'; -import {CdkStep, CdkStepper} from './stepper'; -import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion'; - -@Directive({ - selector: 'cdkStepHeader', - host: { - 'role': 'tab', - '[attr.id]': 'labelId', - '[attr.aria-controls]': 'contentId', - '[attr.aria-selected]': 'selected' - } -}) -export class CdkStepHeader { - /** Whether the orientation of stepper is horizontal. */ - @Input() - get horizontal() { return this._horizontal; } - set horizontal(value: any) { - this._horizontal = coerceBooleanProperty(value); - } - private _horizontal: boolean; - - /** Unique label ID of step header. */ - @Input() - labelId: string; - - /** Unique content ID of step content. */ - @Input() - contentId: string; - - /** Index of the given step. */ - @Input() - get index() { return this._index; } - set index(value: any) { - this._index = coerceNumberProperty(value); - } - private _index: number; - - /** Whether the given step is selected. */ - @Input() - get selected() { return this._selected; } - set selected(value: any) { - this._selected = coerceBooleanProperty(value); - } - private _selected: boolean; - - - /** Returns the step at the index position in stepper. */ - get step(): CdkStep { - return this._stepper._steps.toArray()[this._index]; - } - - constructor(private _stepper: CdkStepper) { } - - /** Returns the type of icon to be displayed. */ - _getIndicatorType(): 'number' | 'edit' | 'done' { - if (!this.step.completed || this._selected) { - return 'number'; - } else { - return this.step.editable ? 'edit' : 'done'; - } - } -} diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index 5b5751a1fbcf..b765d6c2f262 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -20,7 +20,8 @@ import { Component, ContentChild, ViewChild, - TemplateRef + TemplateRef, + ViewEncapsulation } from '@angular/core'; import {LEFT_ARROW, RIGHT_ARROW, ENTER, SPACE} from '@angular/cdk/keycodes'; import {CdkStepLabel} from './step-label'; @@ -53,7 +54,8 @@ export class StepperSelectionEvent { @Component({ selector: 'cdk-step', - templateUrl: 'step.html' + templateUrl: 'step.html', + encapsulation: ViewEncapsulation.None }) export class CdkStep { /** Template for step label if it exists. */ @@ -138,8 +140,8 @@ export class CdkStepper { @Input() get selectedIndex() { return this._selectedIndex; } set selectedIndex(index: number) { - if (index < this._selectedIndex && !this._steps.toArray()[index].editable) { return; } - if (this._anyControlsInvalid(index)) { + if (this._anyControlsInvalid(index) + || index < this._selectedIndex && !this._steps.toArray()[index].editable) { // remove focus from clicked step header if the step is not able to be selected this._stepHeader.toArray()[index].nativeElement.blur(); } else if (this._selectedIndex != index) { @@ -202,6 +204,16 @@ export class CdkStepper { } } + /** Returns the type of icon to be displayed. */ + _getIndicatorType(index: number): 'number' | 'edit' | 'done' { + const step = this._steps.toArray()[index]; + if (!step.completed || this._selectedIndex == index) { + return 'number'; + } else { + return step.editable ? 'edit' : 'done'; + } + } + private _emitStepperSelectionEvent(newIndex: number): void { const stepsArray = this._steps.toArray(); this.selectionChange.emit({ diff --git a/src/lib/stepper/_stepper-theme.scss b/src/lib/stepper/_stepper-theme.scss index b7f0467251e1..776c3bbb8ddd 100644 --- a/src/lib/stepper/_stepper-theme.scss +++ b/src/lib/stepper/_stepper-theme.scss @@ -13,8 +13,8 @@ background-color: mat-color($background, hover); } } - .mat-step-header { + .mat-step-header { .mat-step-label-active { color: mat-color($foreground, text); } diff --git a/src/lib/stepper/index.ts b/src/lib/stepper/index.ts index b5dbb19d1332..416ee5d2295a 100644 --- a/src/lib/stepper/index.ts +++ b/src/lib/stepper/index.ts @@ -19,7 +19,6 @@ import {MdStepLabel} from './step-label'; import {MdStepperNext, MdStepperPrevious} from './stepper-button'; import {MdIconModule} from '../icon/index'; import {MdStepHeader} from './step-header'; -import {MdStepContent} from './step-content'; @NgModule({ imports: [ @@ -31,9 +30,9 @@ import {MdStepContent} from './step-content'; MdIconModule ], exports: [MdCommonModule, MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper, - MdStepperNext, MdStepperPrevious, MdStepHeader, MdStepContent], + MdStepperNext, MdStepperPrevious, MdStepHeader], declarations: [MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper, - MdStepperNext, MdStepperPrevious, MdStepHeader, MdStepContent], + MdStepperNext, MdStepperPrevious, MdStepHeader], }) export class MdStepperModule {} @@ -43,4 +42,3 @@ export * from './step-label'; export * from './stepper'; export * from './stepper-button'; export * from './step-header'; -export * from './step-content'; diff --git a/src/lib/stepper/step-content.html b/src/lib/stepper/step-content.html deleted file mode 100644 index 93615940c98e..000000000000 --- a/src/lib/stepper/step-content.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/src/lib/stepper/step-content.scss b/src/lib/stepper/step-content.scss deleted file mode 100644 index 784ea496d427..000000000000 --- a/src/lib/stepper/step-content.scss +++ /dev/null @@ -1,6 +0,0 @@ -$mat-stepper-side-gap: 24px !default; - -.mat-vertical-content { - padding: 0 $mat-stepper-side-gap $mat-stepper-side-gap $mat-stepper-side-gap; - overflow: hidden; -} diff --git a/src/lib/stepper/step-content.ts b/src/lib/stepper/step-content.ts deleted file mode 100644 index 8fdd34ab9dc1..000000000000 --- a/src/lib/stepper/step-content.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {Component} from '@angular/core'; -import {CdkStepContent} from '@angular/cdk/stepper'; -import {MdStepper} from './stepper'; - -@Component({ - selector: 'md-step-content, mat-step-content', - templateUrl: 'step-content.html', - styleUrls: ['step-content.css'], - host: { - '[class.mat-vertical-stepper-content]': '!horizontal', - '[class.mat-horizontal-stepper-content]': 'horizontal', - 'role': 'tabpanel', - '[attr.id]': 'contentId', - '[attr.aria-labelledby]': 'labelId', - '[attr.aria-expanded]': 'selectedIndex == index', - }, -}) -export class MdStepContent extends CdkStepContent { - constructor(mdStepper: MdStepper) { - super(mdStepper); - } -} diff --git a/src/lib/stepper/step-header.html b/src/lib/stepper/step-header.html index fa8a7d353a85..e1880be39c4a 100644 --- a/src/lib/stepper/step-header.html +++ b/src/lib/stepper/step-header.html @@ -1,20 +1,17 @@ -
-
- {{index + 1}} - create - done -
-
- - - - -
{{step.label}}
+
+ {{index + 1}} + create + done +
+
+ + + + +
{{label}}
-
Optional
-
+
Optional
+ diff --git a/src/lib/stepper/step-header.scss b/src/lib/stepper/step-header.scss index 5ac7201681bd..d85b71b5e7fa 100644 --- a/src/lib/stepper/step-header.scss +++ b/src/lib/stepper/step-header.scss @@ -1,4 +1,3 @@ -$mat-horizontal-stepper-header-height: 72px !default; $mat-stepper-label-header-height: 24px !default; $mat-stepper-label-min-width: 50px !default; $mat-stepper-side-gap: 24px !default; @@ -10,32 +9,6 @@ $mat-step-optional-font-size: 12px; display: flex; } -.mat-horizontal-stepper-header { - display: flex; - height: $mat-horizontal-stepper-header-height; - overflow: hidden; - align-items: center; - padding: 0 $mat-stepper-side-gap; - - .mat-step-icon, - .mat-step-icon-not-touched { - margin-right: $mat-stepper-line-gap; - flex: none; - } -} - -.mat-vertical-stepper-header { - display: flex; - align-items: center; - padding: $mat-stepper-side-gap; - max-height: $mat-stepper-label-header-height; - - .mat-step-icon, - .mat-step-icon-not-touched { - margin-right: $mat-vertical-stepper-content-margin - $mat-stepper-side-gap; - } -} - .mat-step-optional { font-size: $mat-step-optional-font-size; } @@ -60,7 +33,7 @@ $mat-step-optional-font-size: 12px; vertical-align: middle; } -.text-label { +.mat-step-text-label { text-overflow: ellipsis; overflow: hidden; } diff --git a/src/lib/stepper/step-header.ts b/src/lib/stepper/step-header.ts index d7612c6ccf68..24dd72275ed2 100644 --- a/src/lib/stepper/step-header.ts +++ b/src/lib/stepper/step-header.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component} from '@angular/core'; -import {CdkStepHeader} from '@angular/cdk/stepper'; -import {MdStepper} from './stepper'; +import {Component, Input, ViewEncapsulation} from '@angular/core'; +import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion'; +import {MdStepLabel} from './step-label'; @Component({ selector: 'md-step-header, mat-step-header', @@ -20,10 +20,67 @@ import {MdStepper} from './stepper'; '[attr.id]': 'labelId', '[attr.aria-controls]': 'contentId', '[attr.aria-selected]': 'selected' - } + }, + encapsulation: ViewEncapsulation.None }) -export class MdStepHeader extends CdkStepHeader { - constructor(mdStepper: MdStepper) { - super(mdStepper); +export class MdStepHeader { + /** Unique label ID of step header. */ + @Input() + labelId: string; + + /** Unique content ID of step content. */ + @Input() + contentId: string; + + /** Icon for the given step. */ + @Input() + icon: string; + + /** Text label of the given step. */ + @Input() + get label() { return this._label; } + set label(value: any) { + this._label = value; + } + private _label: string; + + /** Templated label of the given step. */ + @Input() + get stepLabel() { return this._stepLabel; } + set stepLabel(value: any) { + this._stepLabel = value; + } + private _stepLabel: MdStepLabel; + + /** Index of the given step. */ + @Input() + get index() { return this._index; } + set index(value: any) { + this._index = coerceNumberProperty(value); + } + private _index: number; + + /** Whether the given step is selected. */ + @Input() + get selected() { return this._selected; } + set selected(value: any) { + this._selected = coerceBooleanProperty(value); + } + private _selected: boolean; + + /** Whether the given step label is active. */ + @Input() + get active() { return this._active; } + set active(value: any) { + this._active = coerceBooleanProperty(value); + } + private _active: boolean; + + /** Whether the given step is optional. */ + @Input() + get optional() { return this._optional; } + set optional(value: any) { + this._optional = coerceBooleanProperty(value); } + private _optional: boolean; } diff --git a/src/lib/stepper/stepper-horizontal.html b/src/lib/stepper/stepper-horizontal.html index 6f8c0e7b40f8..e6535038c916 100644 --- a/src/lib/stepper/stepper-horizontal.html +++ b/src/lib/stepper/stepper-horizontal.html @@ -2,11 +2,16 @@
- + [icon]="_getIndicatorType(i)" + [stepLabel]="step.stepLabel" + [label]="step.label" + [selected]="selectedIndex == i" + [active]="step.completed || selectedIndex == i" + [optional]="step.optional">
@@ -14,11 +19,12 @@
- +
+ +
diff --git a/src/lib/stepper/stepper-horizontal.ts b/src/lib/stepper/stepper-horizontal.ts index dbccf06a60c8..776337a8aa5f 100644 --- a/src/lib/stepper/stepper-horizontal.ts +++ b/src/lib/stepper/stepper-horizontal.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component} from '@angular/core'; +import {Component, ViewEncapsulation} from '@angular/core'; import {MdStepper} from './stepper'; import {animate, state, style, transition, trigger} from '@angular/animations'; @@ -29,6 +29,7 @@ import {animate, state, style, transition, trigger} from '@angular/animations'; animate('500ms cubic-bezier(0.35, 0, 0.25, 1)')) ]) ], - providers: [{provide: MdStepper, useExisting: MdHorizontalStepper}] + providers: [{provide: MdStepper, useExisting: MdHorizontalStepper}], + encapsulation: ViewEncapsulation.None }) export class MdHorizontalStepper extends MdStepper { } diff --git a/src/lib/stepper/stepper-vertical.html b/src/lib/stepper/stepper-vertical.html index 53e84e8ac1a6..7dd40833dddb 100644 --- a/src/lib/stepper/stepper-vertical.html +++ b/src/lib/stepper/stepper-vertical.html @@ -1,21 +1,28 @@
- - + [icon]="_getIndicatorType(i)" + [stepLabel]="step.stepLabel" + [label]="step.label" + [selected]="selectedIndex == i" + [active]="step.completed || selectedIndex == i" + [optional]="step.optional">
- +
+
+ +
+
diff --git a/src/lib/stepper/stepper-vertical.ts b/src/lib/stepper/stepper-vertical.ts index b72349db888c..9f85984e74ef 100644 --- a/src/lib/stepper/stepper-vertical.ts +++ b/src/lib/stepper/stepper-vertical.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component} from '@angular/core'; +import {Component, ViewEncapsulation} from '@angular/core'; import {MdStepper} from './stepper'; import {animate, state, style, transition, trigger} from '@angular/animations'; @@ -28,6 +28,7 @@ import {animate, state, style, transition, trigger} from '@angular/animations'; transition('* <=> current', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')) ]) ], - providers: [{provide: MdStepper, useExisting: MdVerticalStepper}] + providers: [{provide: MdStepper, useExisting: MdVerticalStepper}], + encapsulation: ViewEncapsulation.None }) export class MdVerticalStepper extends MdStepper { } diff --git a/src/lib/stepper/stepper.scss b/src/lib/stepper/stepper.scss index cf13ec8f9234..6f250a8456c1 100644 --- a/src/lib/stepper/stepper.scss +++ b/src/lib/stepper/stepper.scss @@ -1,5 +1,7 @@ @import '../core/style/variables'; +$mat-horizontal-stepper-header-height: 72px !default; +$mat-stepper-label-header-height: 24px !default; $mat-stepper-side-gap: 24px !default; $mat-vertical-stepper-content-margin: 36px !default; $mat-stepper-line-width: 1px !default; @@ -29,9 +31,34 @@ $mat-stepper-line-gap: 8px !default; min-width: $mat-stepper-line-gap + $mat-stepper-side-gap; } -.mat-horizontal-stepper-content { +.mat-horizontal-stepper-header { + display: flex; + height: $mat-horizontal-stepper-header-height; overflow: hidden; + align-items: center; + padding: 0 $mat-stepper-side-gap; + + .mat-step-icon, + .mat-step-icon-not-touched { + margin-right: $mat-stepper-line-gap; + flex: none; + } +} + +.mat-vertical-stepper-header { display: flex; + align-items: center; + padding: $mat-stepper-side-gap; + max-height: $mat-stepper-label-header-height; + + .mat-step-icon, + .mat-step-icon-not-touched { + margin-right: $mat-vertical-stepper-content-margin - $mat-stepper-side-gap; + } +} + +.mat-horizontal-stepper-content { + overflow: hidden; &[aria-expanded='false'] { height: 0; @@ -60,10 +87,13 @@ $mat-stepper-line-gap: 8px !default; } .mat-vertical-stepper-content { - display: flex; overflow: hidden; } +.mat-vertical-content { + padding: 0 $mat-stepper-side-gap $mat-stepper-side-gap $mat-stepper-side-gap; +} + .mat-step:last-child { .mat-vertical-content-container { border: none; diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index 333a8d06016f..a017304de95d 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -564,16 +564,14 @@ function assertCorrectStepIcon(stepperComponent: MdStepper, fixture: ComponentFixture, isEditable: boolean, icon: String) { - let stepHeaderComponent = fixture.debugElement - .queryAll(By.css('md-step-header'))[0].componentInstance; let nextButtonNativeEl = fixture.debugElement .queryAll(By.directive(MdStepperNext))[0].nativeElement; - expect(stepHeaderComponent._getIndicatorType()).toBe('number'); + expect(stepperComponent._getIndicatorType(0)).toBe('number'); stepperComponent._steps.toArray()[0].editable = isEditable; nextButtonNativeEl.click(); fixture.detectChanges(); - expect(stepHeaderComponent._getIndicatorType()).toBe(icon); + expect(stepperComponent._getIndicatorType(0)).toBe(icon); } @Component({ diff --git a/src/lib/stepper/stepper.ts b/src/lib/stepper/stepper.ts index a68241050e8d..e421ca4a7129 100644 --- a/src/lib/stepper/stepper.ts +++ b/src/lib/stepper/stepper.ts @@ -19,7 +19,7 @@ import { Optional, QueryList, SkipSelf, - ViewChildren + ViewChildren, ViewEncapsulation } from '@angular/core'; import {MdStepLabel} from './step-label'; import { @@ -34,7 +34,8 @@ import {FormControl, FormGroupDirective, NgForm} from '@angular/forms'; moduleId: module.id, selector: 'md-step, mat-step', templateUrl: 'step.html', - providers: [{provide: MD_ERROR_GLOBAL_OPTIONS, useExisting: MdStep}] + providers: [{provide: MD_ERROR_GLOBAL_OPTIONS, useExisting: MdStep}], + encapsulation: ViewEncapsulation.None }) export class MdStep extends CdkStep implements ErrorOptions { /** Content for step label given by or . */ From c8a71f454f0a208113620b13fe02319c5634629d Mon Sep 17 00:00:00 2001 From: Ji Won Shin Date: Tue, 22 Aug 2017 10:11:20 -0700 Subject: [PATCH 7/8] Changes to step-header based on review --- src/lib/stepper/_stepper-theme.scss | 4 +-- src/lib/stepper/step-header.html | 4 +-- src/lib/stepper/step-header.scss | 13 +++++++--- src/lib/stepper/step-header.ts | 33 +++++++++---------------- src/lib/stepper/stepper-horizontal.html | 28 ++++++++++----------- src/lib/stepper/stepper-vertical.html | 28 ++++++++++----------- src/lib/stepper/stepper.scss | 2 +- src/lib/stepper/stepper.spec.ts | 4 +-- src/lib/stepper/stepper.ts | 6 +++-- 9 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/lib/stepper/_stepper-theme.scss b/src/lib/stepper/_stepper-theme.scss index 776c3bbb8ddd..ed5042d3b031 100644 --- a/src/lib/stepper/_stepper-theme.scss +++ b/src/lib/stepper/_stepper-theme.scss @@ -7,14 +7,12 @@ $background: map-get($theme, background); $primary: map-get($theme, primary); - .mat-stepper-header { + .mat-step-header { &:focus, &:hover { background-color: mat-color($background, hover); } - } - .mat-step-header { .mat-step-label-active { color: mat-color($foreground, text); } diff --git a/src/lib/stepper/step-header.html b/src/lib/stepper/step-header.html index e1880be39c4a..3f2310862f6f 100644 --- a/src/lib/stepper/step-header.html +++ b/src/lib/stepper/step-header.html @@ -7,10 +7,10 @@
- + -
{{label}}
+
{{label}}
Optional
diff --git a/src/lib/stepper/step-header.scss b/src/lib/stepper/step-header.scss index d85b71b5e7fa..94496b4adcfc 100644 --- a/src/lib/stepper/step-header.scss +++ b/src/lib/stepper/step-header.scss @@ -4,6 +4,7 @@ $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-header-icon-size: 16px !default; :host { display: flex; @@ -18,9 +19,15 @@ $mat-step-optional-font-size: 12px; border-radius: 50%; height: $mat-stepper-label-header-height; width: $mat-stepper-label-header-height; - text-align: center; - line-height: $mat-stepper-label-header-height; - display: inline-block; + align-items: center; + justify-content: center; + display: flex; +} + +.mat-step-icon .mat-icon { + font-size: $mat-step-header-icon-size; + height: $mat-step-header-icon-size; + width: $mat-step-header-icon-size; } .mat-step-label-active, diff --git a/src/lib/stepper/step-header.ts b/src/lib/stepper/step-header.ts index 24dd72275ed2..1e02a4cdcaf7 100644 --- a/src/lib/stepper/step-header.ts +++ b/src/lib/stepper/step-header.ts @@ -17,40 +17,21 @@ import {MdStepLabel} from './step-label'; host: { 'class': 'mat-step-header', 'role': 'tab', - '[attr.id]': 'labelId', - '[attr.aria-controls]': 'contentId', - '[attr.aria-selected]': 'selected' }, encapsulation: ViewEncapsulation.None }) export class MdStepHeader { - /** Unique label ID of step header. */ - @Input() - labelId: string; - - /** Unique content ID of step content. */ - @Input() - contentId: string; - /** Icon for the given step. */ @Input() icon: string; - /** Text label of the given step. */ + /** Label of the given step. */ @Input() get label() { return this._label; } set label(value: any) { this._label = value; } - private _label: string; - - /** Templated label of the given step. */ - @Input() - get stepLabel() { return this._stepLabel; } - set stepLabel(value: any) { - this._stepLabel = value; - } - private _stepLabel: MdStepLabel; + private _label: MdStepLabel | string; /** Index of the given step. */ @Input() @@ -83,4 +64,14 @@ export class MdStepHeader { this._optional = coerceBooleanProperty(value); } private _optional: boolean; + + /** Returns string label of given step if it is a text label. */ + get _stringLabel(): string | null { + return this.label instanceof MdStepLabel ? null : this.label; + } + + /** Returns MdStepLabel if the label of given step is a template label. */ + get _templateLabel(): MdStepLabel | null { + return this.label instanceof MdStepLabel ? this.label : null; + } } diff --git a/src/lib/stepper/stepper-horizontal.html b/src/lib/stepper/stepper-horizontal.html index e6535038c916..ba945010b635 100644 --- a/src/lib/stepper/stepper-horizontal.html +++ b/src/lib/stepper/stepper-horizontal.html @@ -1,19 +1,19 @@
-
- - -
+ +
diff --git a/src/lib/stepper/stepper-vertical.html b/src/lib/stepper/stepper-vertical.html index 7dd40833dddb..426c03b250c2 100644 --- a/src/lib/stepper/stepper-vertical.html +++ b/src/lib/stepper/stepper-vertical.html @@ -1,18 +1,18 @@
-
- - -
+ +
) { let stepHeaderEl = fixture.debugElement - .queryAll(By.css('.mat-stepper-header'))[1].nativeElement; + .queryAll(By.css('md-step-header'))[1].nativeElement; let nextButtonNativeEl = fixture.debugElement .queryAll(By.directive(MdStepperNext))[0].nativeElement; spyOn(stepHeaderEl, 'focus'); @@ -491,7 +491,7 @@ function assertLinearStepperValidity(stepHeaderEl: HTMLElement, /** Asserts that step header focus is blurred if the step cannot be selected upon header click. */ function assertStepHeaderBlurred(fixture: ComponentFixture) { let stepHeaderEl = fixture.debugElement - .queryAll(By.css('.mat-stepper-header'))[1].nativeElement; + .queryAll(By.css('md-step-header'))[1].nativeElement; spyOn(stepHeaderEl, 'blur'); stepHeaderEl.click(); fixture.detectChanges(); diff --git a/src/lib/stepper/stepper.ts b/src/lib/stepper/stepper.ts index e421ca4a7129..9492a4a32a8d 100644 --- a/src/lib/stepper/stepper.ts +++ b/src/lib/stepper/stepper.ts @@ -19,7 +19,8 @@ import { Optional, QueryList, SkipSelf, - ViewChildren, ViewEncapsulation + ViewChildren, + ViewEncapsulation } from '@angular/core'; import {MdStepLabel} from './step-label'; import { @@ -29,6 +30,7 @@ import { ErrorStateMatcher } from '../core/error/error-options'; import {FormControl, FormGroupDirective, NgForm} from '@angular/forms'; +import {MdStepHeader} from './step-header'; @Component({ moduleId: module.id, @@ -67,7 +69,7 @@ export class MdStep extends CdkStep implements ErrorOptions { export class MdStepper extends CdkStepper implements ErrorOptions { /** The list of step headers of the steps in the stepper. */ - @ViewChildren('stepperHeader') _stepHeader: QueryList; + @ViewChildren(MdStepHeader, {read: ElementRef}) _stepHeader: QueryList; /** Steps that the stepper holds. */ @ContentChildren(MdStep) _steps: QueryList; From 1a5af09f409ff0a915c7c3a13c397dcce7928243 Mon Sep 17 00:00:00 2001 From: Ji Won Shin Date: Tue, 22 Aug 2017 10:36:53 -0700 Subject: [PATCH 8/8] Minor changes --- src/lib/stepper/step-header.ts | 6 +----- src/lib/stepper/stepper-horizontal.html | 2 +- src/lib/stepper/stepper-vertical.html | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/lib/stepper/step-header.ts b/src/lib/stepper/step-header.ts index 1e02a4cdcaf7..14901227f698 100644 --- a/src/lib/stepper/step-header.ts +++ b/src/lib/stepper/step-header.ts @@ -27,11 +27,7 @@ export class MdStepHeader { /** Label of the given step. */ @Input() - get label() { return this._label; } - set label(value: any) { - this._label = value; - } - private _label: MdStepLabel | string; + label: MdStepLabel | string; /** Index of the given step. */ @Input() diff --git a/src/lib/stepper/stepper-horizontal.html b/src/lib/stepper/stepper-horizontal.html index ba945010b635..5e2d2ea80fa7 100644 --- a/src/lib/stepper/stepper-horizontal.html +++ b/src/lib/stepper/stepper-horizontal.html @@ -9,7 +9,7 @@ [attr.aria-selected]="selectedIndex == i" [index]="i" [icon]="_getIndicatorType(i)" - [label]="step.stepLabel ? step.stepLabel : step.label" + [label]="step.stepLabel || step.label" [selected]="selectedIndex == i" [active]="step.completed || selectedIndex == i" [optional]="step.optional"> diff --git a/src/lib/stepper/stepper-vertical.html b/src/lib/stepper/stepper-vertical.html index 426c03b250c2..e32bf82b4f23 100644 --- a/src/lib/stepper/stepper-vertical.html +++ b/src/lib/stepper/stepper-vertical.html @@ -8,7 +8,7 @@ [attr.aria-selected]="selectedIndex == i" [index]="i" [icon]="_getIndicatorType(i)" - [label]="step.stepLabel ? step.stepLabel : step.label" + [label]="step.stepLabel || step.label" [selected]="selectedIndex == i" [active]="step.completed || selectedIndex == i" [optional]="step.optional">