diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts
index 089555589f2..eb0b5b9b444 100644
--- a/src/cdk/stepper/stepper.ts
+++ b/src/cdk/stepper/stepper.ts
@@ -30,7 +30,7 @@ import {LEFT_ARROW, RIGHT_ARROW, ENTER, SPACE} from '@angular/cdk/keycodes';
import {CdkStepLabel} from './step-label';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {AbstractControl} from '@angular/forms';
-import {Directionality} from '@angular/cdk/bidi';
+import {Direction, Directionality} from '@angular/cdk/bidi';
/** Used to generate unique ID for each stepper component. */
let nextId = 0;
@@ -151,8 +151,7 @@ export class CdkStepper {
@Input()
get selected() { return this._steps[this.selectedIndex]; }
set selected(step: CdkStep) {
- let index = this._steps.toArray().indexOf(step);
- this.selectedIndex = index;
+ this.selectedIndex = this._steps.toArray().indexOf(step);
}
/** Event emitted when the selected step has changed. */
@@ -192,12 +191,11 @@ export class CdkStepper {
_getAnimationDirection(index: number): StepContentPositionState {
const position = index - this._selectedIndex;
if (position < 0) {
- return 'previous';
+ return this._layoutDirection() === 'rtl' ? 'next' : 'previous';
} else if (position > 0) {
- return 'next';
- } else {
- return 'current';
+ return this._layoutDirection() === 'rtl' ? 'previous' : 'next';
}
+ return 'current';
}
/** Returns the type of icon to be displayed. */
@@ -224,17 +222,17 @@ export class CdkStepper {
_onKeydown(event: KeyboardEvent) {
switch (event.keyCode) {
case RIGHT_ARROW:
- if (this._dir && this._dir.value === 'rtl') {
- this._focusStep((this._focusIndex + this._steps.length - 1) % this._steps.length);
+ if (this._layoutDirection() === 'rtl') {
+ this._focusPreviousStep();
} else {
- this._focusStep((this._focusIndex + 1) % this._steps.length);
+ this._focusNextStep();
}
break;
case LEFT_ARROW:
- if (this._dir && this._dir.value === 'rtl') {
- this._focusStep((this._focusIndex + 1) % this._steps.length);
+ if (this._layoutDirection() === 'rtl') {
+ this._focusNextStep();
} else {
- this._focusStep((this._focusIndex + this._steps.length - 1) % this._steps.length);
+ this._focusPreviousStep();
}
break;
case SPACE:
@@ -248,17 +246,28 @@ export class CdkStepper {
event.preventDefault();
}
+ private _focusNextStep() {
+ this._focusStep((this._focusIndex + 1) % this._steps.length);
+ }
+
+ private _focusPreviousStep() {
+ this._focusStep((this._focusIndex + this._steps.length - 1) % this._steps.length);
+ }
+
private _focusStep(index: number) {
this._focusIndex = index;
this._stepHeader.toArray()[this._focusIndex].nativeElement.focus();
}
private _anyControlsInvalid(index: number): boolean {
- const stepsArray = this._steps.toArray();
- stepsArray[this._selectedIndex].interacted = true;
- if (this._linear) {
- return stepsArray.slice(0, index).some(step => step.stepControl.invalid);
+ this._steps.toArray()[this._selectedIndex].interacted = true;
+ if (this._linear && index >= 0) {
+ return this._steps.toArray().slice(0, index).some(step => step.stepControl.invalid);
}
return false;
}
+
+ private _layoutDirection(): Direction {
+ return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
+ }
}
diff --git a/src/demo-app/stepper/stepper-demo.ts b/src/demo-app/stepper/stepper-demo.ts
index 77cfb353591..373c298c5fd 100644
--- a/src/demo-app/stepper/stepper-demo.ts
+++ b/src/demo-app/stepper/stepper-demo.ts
@@ -1,8 +1,6 @@
import {Component} from '@angular/core';
import {AbstractControl, 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',
@@ -36,7 +34,7 @@ export class StepperDemo {
lastNameFormCtrl: ['', Validators.required],
}),
this._formBuilder.group({
- emailFormCtrl: ['', Validators.pattern(EMAIL_REGEX)]
+ emailFormCtrl: ['', Validators.email]
}),
])
});
@@ -47,7 +45,7 @@ export class StepperDemo {
});
this.emailFormGroup = this._formBuilder.group({
- emailCtrl: ['', Validators.pattern(EMAIL_REGEX)]
+ emailCtrl: ['', Validators.email]
});
}
}
diff --git a/src/lib/stepper/_stepper-theme.scss b/src/lib/stepper/_stepper-theme.scss
index fc4e339a49c..c7b17278a12 100644
--- a/src/lib/stepper/_stepper-theme.scss
+++ b/src/lib/stepper/_stepper-theme.scss
@@ -13,11 +13,7 @@
background-color: mat-color($background, hover);
}
- .mat-step-label-active {
- color: mat-color($foreground, text);
- }
-
- .mat-step-label-inactive,
+ .mat-step-label,
.mat-step-optional {
color: mat-color($foreground, disabled-text);
}
@@ -31,6 +27,10 @@
background-color: mat-color($foreground, disabled-text);
color: mat-color($primary, default-contrast);
}
+
+ .mat-step-label.mat-step-label-active {
+ color: mat-color($foreground, text);
+ }
}
.mat-stepper-horizontal, .mat-stepper-vertical {
diff --git a/src/lib/stepper/index.ts b/src/lib/stepper/index.ts
index a574fbc5399..46ba69495ac 100644
--- a/src/lib/stepper/index.ts
+++ b/src/lib/stepper/index.ts
@@ -27,8 +27,17 @@ import {MdStepHeader} from './step-header';
CdkStepperModule,
MdIconModule
],
- exports: [MdCommonModule, MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper,
- MdStepperNext, MdStepperPrevious, MdStepHeader],
+ exports: [
+ MdCommonModule,
+ MdHorizontalStepper,
+ MdVerticalStepper,
+ MdStep,
+ MdStepLabel,
+ MdStepper,
+ MdStepperNext,
+ MdStepperPrevious,
+ MdStepHeader
+ ],
declarations: [MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper,
MdStepperNext, MdStepperPrevious, MdStepHeader],
})
diff --git a/src/lib/stepper/step-header.html b/src/lib/stepper/step-header.html
index 3f2310862f6..b385c5b4397 100644
--- a/src/lib/stepper/step-header.html
+++ b/src/lib/stepper/step-header.html
@@ -1,16 +1,17 @@
-
-
{{index + 1}}
-
create
-
done
+
+ {{index + 1}}
+ create
+ done
-
+
-
+
- {{label}}
+ {{label}}
Optional
diff --git a/src/lib/stepper/step-header.scss b/src/lib/stepper/step-header.scss
index 9313a57e4a4..33cb813902a 100644
--- a/src/lib/stepper/step-header.scss
+++ b/src/lib/stepper/step-header.scss
@@ -26,8 +26,7 @@ $mat-step-header-icon-size: 16px !default;
width: $mat-step-header-icon-size;
}
-.mat-step-label-active,
-.mat-step-label-inactive {
+.mat-step-label {
display: inline-block;
white-space: nowrap;
overflow: hidden;
diff --git a/src/lib/stepper/step-header.ts b/src/lib/stepper/step-header.ts
index b6624e2424c..ae79e5cdf70 100644
--- a/src/lib/stepper/step-header.ts
+++ b/src/lib/stepper/step-header.ts
@@ -23,12 +23,10 @@ import {MdStepLabel} from './step-label';
})
export class MdStepHeader {
/** Icon for the given step. */
- @Input()
- icon: string;
+ @Input() icon: string;
/** Label of the given step. */
- @Input()
- label: MdStepLabel | string;
+ @Input() label: MdStepLabel | string;
/** Index of the given step. */
@Input()
@@ -63,12 +61,12 @@ export class MdStepHeader {
private _optional: boolean;
/** Returns string label of given step if it is a text label. */
- get _stringLabel(): string | null {
+ _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 {
+ _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 5e2d2ea80fa..8b32e92e93a 100644
--- a/src/lib/stepper/stepper-horizontal.html
+++ b/src/lib/stepper/stepper-horizontal.html
@@ -3,15 +3,15 @@
@@ -24,7 +24,7 @@
[@stepTransition]="_getAnimationDirection(i)"
[id]="_getStepContentId(i)"
[attr.aria-labelledby]="_getStepLabelId(i)"
- [attr.aria-expanded]="selectedIndex == i">
+ [attr.aria-expanded]="selectedIndex === i">
diff --git a/src/lib/stepper/stepper-vertical.html b/src/lib/stepper/stepper-vertical.html
index b73bed61929..bccd9bf5f4d 100644
--- a/src/lib/stepper/stepper-vertical.html
+++ b/src/lib/stepper/stepper-vertical.html
@@ -5,12 +5,12 @@
[tabIndex]="_focusIndex == i ? 0 : -1"
[id]="_getStepLabelId(i)"
[attr.aria-controls]="_getStepContentId(i)"
- [attr.aria-selected]="selectedIndex == i"
+ [attr.aria-selected]="selectedIndex === i"
[index]="i"
[icon]="_getIndicatorType(i)"
[label]="step.stepLabel || step.label"
- [selected]="selectedIndex == i"
- [active]="step.completed || selectedIndex == i"
+ [selected]="selectedIndex === i"
+ [active]="step.completed || selectedIndex === i"
[optional]="step.optional">
@@ -19,7 +19,7 @@
[@stepTransition]="_getAnimationDirection(i)"
[id]="_getStepContentId(i)"
[attr.aria-labelledby]="_getStepLabelId(i)"
- [attr.aria-expanded]="selectedIndex == i">
+ [attr.aria-expanded]="selectedIndex === i">
diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts
index b5965f9d475..13c357d80b6 100644
--- a/src/lib/stepper/stepper.spec.ts
+++ b/src/lib/stepper/stepper.spec.ts
@@ -8,16 +8,22 @@ import {MdStepperNext, MdStepperPrevious} from './stepper-button';
import {dispatchKeyboardEvent} from '@angular/cdk/testing';
import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
import {MdStepper, MdHorizontalStepper, MdVerticalStepper} from './stepper';
+import {Directionality} from '../core';
-const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
+const VALID_REGEX = /valid/;
describe('MdHorizontalStepper', () => {
+ let dir = 'ltr';
+
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdStepperModule, NoopAnimationsModule, ReactiveFormsModule],
declarations: [
SimpleMdHorizontalStepperApp,
LinearMdHorizontalStepperApp
+ ],
+ providers: [
+ {provide: Directionality, useFactory: () => ({value: dir})}
]
});
@@ -26,23 +32,21 @@ describe('MdHorizontalStepper', () => {
describe('basic horizontal stepper', () => {
let fixture: ComponentFixture;
- let stepperComponent: MdHorizontalStepper;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleMdHorizontalStepperApp);
fixture.detectChanges();
-
- stepperComponent = fixture.debugElement
- .query(By.css('md-horizontal-stepper')).componentInstance;
});
it('should default to the first step', () => {
+ let stepperComponent = fixture.debugElement
+ .query(By.css('md-horizontal-stepper')).componentInstance;
expect(stepperComponent.selectedIndex).toBe(0);
});
it('should change selected index on header click', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
- assertSelectionChangeOnHeaderClick(stepperComponent, fixture, stepHeaders);
+ assertSelectionChangeOnHeaderClick(fixture, stepHeaders);
});
it('should set the "tablist" role on stepper', () => {
@@ -52,44 +56,63 @@ describe('MdHorizontalStepper', () => {
it('should set aria-expanded of content correctly', () => {
let stepContents = fixture.debugElement.queryAll(By.css(`.mat-horizontal-stepper-content`));
- assertCorrectAriaExpandedAttribute(stepperComponent, fixture, stepContents);
+ assertCorrectAriaExpandedAttribute(fixture, stepContents);
});
it('should display the correct label', () => {
- assertCorrectStepLabel(stepperComponent, fixture);
+ assertCorrectStepLabel(fixture);
});
it('should go to next available step when the next button is clicked', () => {
- assertNextStepperButtonClick(stepperComponent, fixture);
+ assertNextStepperButtonClick(fixture);
});
it('should go to previous available step when the previous button is clicked', () => {
- assertPreviousStepperButtonClick(stepperComponent, fixture);
+ assertPreviousStepperButtonClick(fixture);
});
it('should set the correct step position for animation', () => {
- assertCorrectStepPosition(stepperComponent, fixture);
+ assertCorrectStepAnimationDirection(fixture);
});
it('should support keyboard events to move and select focus', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
- assertCorrectKeyboardInteraction(stepperComponent, fixture, stepHeaders);
+ assertCorrectKeyboardInteraction(fixture, stepHeaders);
});
it('should not set focus on header of selected step if header is not clicked', () => {
- assertStepHeaderFocusNotCalled(stepperComponent, fixture);
+ assertStepHeaderFocusNotCalled(fixture);
});
it('should only be able to return to a previous step if it is editable', () => {
- assertEditableStepChange(stepperComponent, fixture);
+ assertEditableStepChange(fixture);
});
it('should set create icon if step is editable and completed', () => {
- assertCorrectStepIcon(stepperComponent, fixture, true, 'edit');
+ assertCorrectStepIcon(fixture, true, 'edit');
});
it('should set done icon if step is not editable and is completed', () => {
- assertCorrectStepIcon(stepperComponent, fixture, false, 'done');
+ assertCorrectStepIcon(fixture, false, 'done');
+ });
+ });
+
+ describe('RTL', () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ dir = 'rtl';
+ fixture = TestBed.createComponent(SimpleMdHorizontalStepperApp);
+ fixture.detectChanges();
+ });
+
+ it('should reverse arrow key focus in RTL mode', () => {
+ let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
+ assertArrowKeyInteractionInRtl(fixture, stepHeaders);
+ });
+
+ it('should reverse animation in RTL mode', () => {
+ assertCorrectStepAnimationDirection(fixture, 'rtl');
});
});
@@ -119,7 +142,7 @@ describe('MdHorizontalStepper', () => {
let stepHeaderEl = fixture.debugElement
.queryAll(By.css('.mat-horizontal-stepper-header'))[1].nativeElement;
- assertLinearStepperValidity(stepHeaderEl, stepperComponent, testComponent, fixture);
+ assertLinearStepperValidity(stepHeaderEl, testComponent, fixture);
});
it('should not focus step header upon click if it is not able to be selected', () => {
@@ -127,18 +150,23 @@ describe('MdHorizontalStepper', () => {
});
it('should be able to move to next step even when invalid if current step is optional', () => {
- assertOptionalStepValidity(stepperComponent, testComponent, fixture);
+ assertOptionalStepValidity(testComponent, fixture);
});
});
});
describe('MdVerticalStepper', () => {
+ let dir = 'ltr';
+
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdStepperModule, NoopAnimationsModule, ReactiveFormsModule],
declarations: [
SimpleMdVerticalStepperApp,
LinearMdVerticalStepperApp
+ ],
+ providers: [
+ {provide: Directionality, useFactory: () => ({value: dir})}
]
});
@@ -147,23 +175,21 @@ describe('MdVerticalStepper', () => {
describe('basic vertical stepper', () => {
let fixture: ComponentFixture;
- let stepperComponent: MdVerticalStepper;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleMdVerticalStepperApp);
fixture.detectChanges();
-
- stepperComponent = fixture.debugElement
- .query(By.css('md-vertical-stepper')).componentInstance;
});
it('should default to the first step', () => {
+ let stepperComponent = fixture.debugElement
+ .query(By.css('md-vertical-stepper')).componentInstance;
expect(stepperComponent.selectedIndex).toBe(0);
});
it('should change selected index on header click', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
- assertSelectionChangeOnHeaderClick(stepperComponent, fixture, stepHeaders);
+ assertSelectionChangeOnHeaderClick(fixture, stepHeaders);
});
@@ -174,44 +200,63 @@ describe('MdVerticalStepper', () => {
it('should set aria-expanded of content correctly', () => {
let stepContents = fixture.debugElement.queryAll(By.css(`.mat-vertical-stepper-content`));
- assertCorrectAriaExpandedAttribute(stepperComponent, fixture, stepContents);
+ assertCorrectAriaExpandedAttribute(fixture, stepContents);
});
it('should display the correct label', () => {
- assertCorrectStepLabel(stepperComponent, fixture);
+ assertCorrectStepLabel(fixture);
});
it('should go to next available step when the next button is clicked', () => {
- assertNextStepperButtonClick(stepperComponent, fixture);
+ assertNextStepperButtonClick(fixture);
});
it('should go to previous available step when the previous button is clicked', () => {
- assertPreviousStepperButtonClick(stepperComponent, fixture);
+ assertPreviousStepperButtonClick(fixture);
});
it('should set the correct step position for animation', () => {
- assertCorrectStepPosition(stepperComponent, fixture);
+ assertCorrectStepAnimationDirection(fixture);
});
it('should support keyboard events to move and select focus', () => {
let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
- assertCorrectKeyboardInteraction(stepperComponent, fixture, stepHeaders);
+ assertCorrectKeyboardInteraction(fixture, stepHeaders);
});
it('should not set focus on header of selected step if header is not clicked', () => {
- assertStepHeaderFocusNotCalled(stepperComponent, fixture);
+ assertStepHeaderFocusNotCalled(fixture);
});
it('should only be able to return to a previous step if it is editable', () => {
- assertEditableStepChange(stepperComponent, fixture);
+ assertEditableStepChange(fixture);
});
it('should set create icon if step is editable and completed', () => {
- assertCorrectStepIcon(stepperComponent, fixture, true, 'edit');
+ assertCorrectStepIcon(fixture, true, 'edit');
});
it('should set done icon if step is not editable and is completed', () => {
- assertCorrectStepIcon(stepperComponent, fixture, false, 'done');
+ assertCorrectStepIcon(fixture, false, 'done');
+ });
+ });
+
+ describe('RTL', () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ dir = 'rtl';
+ fixture = TestBed.createComponent(SimpleMdVerticalStepperApp);
+ fixture.detectChanges();
+ });
+
+ it('should reverse arrow key focus in RTL mode', () => {
+ let stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
+ assertArrowKeyInteractionInRtl(fixture, stepHeaders);
+ });
+
+ it('should reverse animation in RTL mode', () => {
+ assertCorrectStepAnimationDirection(fixture, 'rtl');
});
});
@@ -242,7 +287,7 @@ describe('MdVerticalStepper', () => {
let stepHeaderEl = fixture.debugElement
.queryAll(By.css('.mat-vertical-stepper-header'))[1].nativeElement;
- assertLinearStepperValidity(stepHeaderEl, stepperComponent, testComponent, fixture);
+ assertLinearStepperValidity(stepHeaderEl, testComponent, fixture);
});
it('should not focus step header upon click if it is not able to be selected', () => {
@@ -250,15 +295,16 @@ describe('MdVerticalStepper', () => {
});
it('should be able to move to next step even when invalid if current step is optional', () => {
- assertOptionalStepValidity(stepperComponent, testComponent, fixture);
+ assertOptionalStepValidity(testComponent, fixture);
});
});
});
/** Asserts that `selectedIndex` updates correctly when header of another step is clicked. */
-function assertSelectionChangeOnHeaderClick(stepperComponent: MdStepper,
- fixture: ComponentFixture,
- stepHeaders: DebugElement[]) {
+function assertSelectionChangeOnHeaderClick(fixture: ComponentFixture,
+ stepHeaders: DebugElement[]) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
expect(stepperComponent.selectedIndex).toBe(0);
// select the second step
@@ -277,9 +323,9 @@ function assertSelectionChangeOnHeaderClick(stepperComponent: MdStepper,
}
/** Asserts that 'aria-expanded' attribute is correct for expanded content of step. */
-function assertCorrectAriaExpandedAttribute(stepperComponent: MdStepper,
- fixture: ComponentFixture,
- stepContents: DebugElement[]) {
+function assertCorrectAriaExpandedAttribute(fixture: ComponentFixture,
+ stepContents: DebugElement[]) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
let firstStepContentEl = stepContents[0].nativeElement;
expect(firstStepContentEl.getAttribute('aria-expanded')).toBe('true');
@@ -292,7 +338,8 @@ function assertCorrectAriaExpandedAttribute(stepperComponent: MdStepper,
}
/** Asserts that step has correct label. */
-function assertCorrectStepLabel(stepperComponent: MdStepper, fixture: ComponentFixture) {
+function assertCorrectStepLabel(fixture: ComponentFixture) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
let selectedLabel = fixture.nativeElement.querySelector('[aria-selected="true"]');
expect(selectedLabel.textContent).toMatch('Step 1');
@@ -310,7 +357,9 @@ function assertCorrectStepLabel(stepperComponent: MdStepper, fixture: ComponentF
}
/** Asserts that clicking on MdStepperNext button updates `selectedIndex` correctly. */
-function assertNextStepperButtonClick(stepperComponent: MdStepper, fixture: ComponentFixture) {
+function assertNextStepperButtonClick(fixture: ComponentFixture) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
expect(stepperComponent.selectedIndex).toBe(0);
let nextButtonNativeEl = fixture.debugElement
@@ -336,8 +385,9 @@ function assertNextStepperButtonClick(stepperComponent: MdStepper, fixture: Comp
}
/** Asserts that clicking on MdStepperPrevious button updates `selectedIndex` correctly. */
-function assertPreviousStepperButtonClick(stepperComponent: MdStepper,
- fixture: ComponentFixture) {
+function assertPreviousStepperButtonClick(fixture: ComponentFixture) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
expect(stepperComponent.selectedIndex).toBe(0);
stepperComponent.selectedIndex = 2;
@@ -364,30 +414,48 @@ function assertPreviousStepperButtonClick(stepperComponent: MdStepper,
}
/** Asserts that step position is correct for animation. */
-function assertCorrectStepPosition(stepperComponent: MdStepper, fixture: ComponentFixture) {
+function assertCorrectStepAnimationDirection(fixture: ComponentFixture, rtl?: 'rtl') {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
expect(stepperComponent._getAnimationDirection(0)).toBe('current');
- expect(stepperComponent._getAnimationDirection(1)).toBe('next');
- expect(stepperComponent._getAnimationDirection(2)).toBe('next');
+ if (rtl === 'rtl') {
+ expect(stepperComponent._getAnimationDirection(1)).toBe('previous');
+ expect(stepperComponent._getAnimationDirection(2)).toBe('previous');
+ } else {
+ expect(stepperComponent._getAnimationDirection(1)).toBe('next');
+ expect(stepperComponent._getAnimationDirection(2)).toBe('next');
+ }
stepperComponent.selectedIndex = 1;
fixture.detectChanges();
- expect(stepperComponent._getAnimationDirection(0)).toBe('previous');
+ if (rtl === 'rtl') {
+ expect(stepperComponent._getAnimationDirection(0)).toBe('next');
+ expect(stepperComponent._getAnimationDirection(2)).toBe('previous');
+ } else {
+ expect(stepperComponent._getAnimationDirection(0)).toBe('previous');
+ expect(stepperComponent._getAnimationDirection(2)).toBe('next');
+ }
expect(stepperComponent._getAnimationDirection(1)).toBe('current');
- expect(stepperComponent._getAnimationDirection(2)).toBe('next');
stepperComponent.selectedIndex = 2;
fixture.detectChanges();
- expect(stepperComponent._getAnimationDirection(0)).toBe('previous');
- expect(stepperComponent._getAnimationDirection(1)).toBe('previous');
+ if (rtl === 'rtl') {
+ expect(stepperComponent._getAnimationDirection(0)).toBe('next');
+ expect(stepperComponent._getAnimationDirection(1)).toBe('next');
+ } else {
+ expect(stepperComponent._getAnimationDirection(0)).toBe('previous');
+ expect(stepperComponent._getAnimationDirection(1)).toBe('previous');
+ }
expect(stepperComponent._getAnimationDirection(2)).toBe('current');
}
/** Asserts that keyboard interaction works correctly. */
-function assertCorrectKeyboardInteraction(stepperComponent: MdStepper,
- fixture: ComponentFixture,
- stepHeaders: DebugElement[]) {
+function assertCorrectKeyboardInteraction(fixture: ComponentFixture,
+ stepHeaders: DebugElement[]) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
expect(stepperComponent._focusIndex).toBe(0);
expect(stepperComponent.selectedIndex).toBe(0);
@@ -396,7 +464,7 @@ function assertCorrectKeyboardInteraction(stepperComponent: MdStepper,
fixture.detectChanges();
expect(stepperComponent._focusIndex)
- .toBe(1, 'Expected index of focused step to be increased by 1 after RIGHT_ARROW event.');
+ .toBe(1, 'Expected index of focused step to increase by 1 after RIGHT_ARROW event.');
expect(stepperComponent.selectedIndex)
.toBe(0, 'Expected index of selected step to remain unchanged after RIGHT_ARROW event.');
@@ -408,7 +476,7 @@ function assertCorrectKeyboardInteraction(stepperComponent: MdStepper,
.toBe(1, 'Expected index of focused step to remain unchanged after ENTER event.');
expect(stepperComponent.selectedIndex)
.toBe(1,
- 'Expected index of selected step to change to index of focused step after EVENT event.');
+ 'Expected index of selected step to change to index of focused step after ENTER event.');
stepHeaderEl = stepHeaders[1].nativeElement;
dispatchKeyboardEvent(stepHeaderEl, 'keydown', LEFT_ARROW);
@@ -444,10 +512,9 @@ function assertCorrectKeyboardInteraction(stepperComponent: MdStepper,
}
/** Asserts that step selection change using stepper buttons does not focus step header. */
-function assertStepHeaderFocusNotCalled(stepperComponent: MdStepper,
- fixture: ComponentFixture) {
- let stepHeaderEl = fixture.debugElement
- .queryAll(By.css('md-step-header'))[1].nativeElement;
+function assertStepHeaderFocusNotCalled(fixture: ComponentFixture) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+ let stepHeaderEl = fixture.debugElement.queryAll(By.css('md-step-header'))[1].nativeElement;
let nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MdStepperNext))[0].nativeElement;
spyOn(stepHeaderEl, 'focus');
@@ -458,14 +525,35 @@ function assertStepHeaderFocusNotCalled(stepperComponent: MdStepper,
expect(stepHeaderEl.focus).not.toHaveBeenCalled();
}
+/** Asserts that arrow key direction works correctly in RTL mode. */
+function assertArrowKeyInteractionInRtl(fixture: ComponentFixture,
+ stepHeaders: DebugElement[]) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
+ expect(stepperComponent._focusIndex).toBe(0);
+
+ let stepHeaderEl = stepHeaders[0].nativeElement;
+ dispatchKeyboardEvent(stepHeaderEl, 'keydown', LEFT_ARROW);
+ fixture.detectChanges();
+
+ expect(stepperComponent._focusIndex).toBe(1);
+
+ stepHeaderEl = stepHeaders[1].nativeElement;
+ dispatchKeyboardEvent(stepHeaderEl, 'keydown', RIGHT_ARROW);
+ fixture.detectChanges();
+
+ expect(stepperComponent._focusIndex).toBe(0);
+}
+
/**
* 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,
- fixture: ComponentFixture) {
+ testComponent:
+ LinearMdHorizontalStepperApp | LinearMdVerticalStepperApp,
+ fixture: ComponentFixture) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
stepHeaderEl.click();
fixture.detectChanges();
@@ -498,8 +586,9 @@ function assertStepHeaderBlurred(fixture: ComponentFixture) {
}
/** Asserts that it is only possible to go back to a previous step if the step is editable. */
-function assertEditableStepChange(stepperComponent: MdStepper,
- fixture: ComponentFixture) {
+function assertEditableStepChange(fixture: ComponentFixture) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
stepperComponent.selectedIndex = 1;
stepperComponent._steps.toArray()[0].editable = false;
let previousButtonNativeEl = fixture.debugElement
@@ -520,9 +609,11 @@ function assertEditableStepChange(stepperComponent: MdStepper,
* 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) {
+function assertOptionalStepValidity(testComponent:
+ LinearMdHorizontalStepperApp | LinearMdVerticalStepperApp,
+ fixture: ComponentFixture) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
+
testComponent.oneGroup.get('oneCtrl')!.setValue('input');
testComponent.twoGroup.get('twoCtrl')!.setValue('input');
stepperComponent.selectedIndex = 2;
@@ -548,7 +639,7 @@ function assertOptionalStepValidity(stepperComponent: MdStepper,
expect(stepperComponent.selectedIndex)
.toBe(2, 'Expected selectedIndex to remain unchanged when optional step input is invalid.');
- testComponent.threeGroup.get('threeCtrl')!.setValue('123@gmail.com');
+ testComponent.threeGroup.get('threeCtrl')!.setValue('valid');
nextButtonNativeEl.click();
fixture.detectChanges();
@@ -558,10 +649,10 @@ function assertOptionalStepValidity(stepperComponent: MdStepper,
}
/** Asserts that step header set the correct icon depending on the state of step. */
-function assertCorrectStepIcon(stepperComponent: MdStepper,
- fixture: ComponentFixture,
+function assertCorrectStepIcon(fixture: ComponentFixture,
isEditable: boolean,
icon: String) {
+ let stepperComponent = fixture.debugElement.query(By.directive(MdStepper)).componentInstance;
let nextButtonNativeEl = fixture.debugElement
.queryAll(By.directive(MdStepperNext))[0].nativeElement;
expect(stepperComponent._getIndicatorType(0)).toBe('number');
@@ -663,7 +754,7 @@ class LinearMdHorizontalStepperApp {
twoCtrl: new FormControl('', Validators.required)
});
this.threeGroup = new FormGroup({
- threeCtrl: new FormControl('', Validators.pattern(EMAIL_REGEX))
+ threeCtrl: new FormControl('', Validators.pattern(VALID_REGEX))
});
}
}
@@ -759,7 +850,7 @@ class LinearMdVerticalStepperApp {
twoCtrl: new FormControl('', Validators.required)
});
this.threeGroup = new FormGroup({
- threeCtrl: new FormControl('', Validators.pattern(EMAIL_REGEX))
+ threeCtrl: new FormControl('', Validators.pattern(VALID_REGEX))
});
}
}
diff --git a/src/lib/stepper/stepper.ts b/src/lib/stepper/stepper.ts
index 4ec7fd207d2..6f40496d2ce 100644
--- a/src/lib/stepper/stepper.ts
+++ b/src/lib/stepper/stepper.ts
@@ -55,9 +55,11 @@ export class MdStep extends _MdStep implements ErrorOptions {
constructor(@Inject(forwardRef(() => MdStepper)) mdStepper: MdStepper,
@Optional() @SkipSelf() @Inject(MD_ERROR_GLOBAL_OPTIONS) errorOptions: ErrorOptions) {
super(mdStepper);
- this._originalErrorStateMatcher =
- errorOptions ? errorOptions.errorStateMatcher || defaultErrorStateMatcher
- : defaultErrorStateMatcher;
+ if (errorOptions && errorOptions.errorStateMatcher) {
+ this._originalErrorStateMatcher = errorOptions.errorStateMatcher;
+ } else {
+ this._originalErrorStateMatcher = defaultErrorStateMatcher;
+ }
}
/** Custom error state matcher that additionally checks for validity of interacted form. */