Skip to content

Commit 78c9eb5

Browse files
committed
Add select() for step component + refactor to avoid circular dependency + support cycling using arrow keys
1 parent 836c0ac commit 78c9eb5

12 files changed

+145
-127
lines changed

src/cdk/stepper/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
*/
88

99
import {NgModule} from '@angular/core';
10-
import {CdkStepper} from './stepper';
10+
import {CdkStepper, CdkStep} from './stepper';
1111
import {CommonModule} from '@angular/common';
12-
import {CdkStep} from './step';
1312
import {CdkStepLabel} from './step-label';
1413
import {PortalModule} from '../portal';
1514

@@ -21,5 +20,4 @@ import {PortalModule} from '../portal';
2120
export class CdkStepperModule {}
2221

2322
export * from './stepper';
24-
export * from './step';
2523
export * from './step-label';

src/cdk/stepper/step-label.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {Directive, TemplateRef} from '@angular/core';
1010

1111
@Directive({
12-
selector: '[cdk-step-label]',
12+
selector: 'cdkStepLabel',
1313
})
1414
export class CdkStepLabel {
1515
constructor(public template: TemplateRef<any>) { }

src/cdk/stepper/step.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/cdk/stepper/stepper.ts

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import {
1414
QueryList,
1515
Directive,
1616
ViewChildren,
17-
// tslint doesn't recognize `ElementRef` is used since it's only used as a generic.
18-
// tslint:disable-next-line
19-
ElementRef
17+
// This import is only used to define a generic type. The current TypeScript version incorrectly
18+
// considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953)
19+
// tslint:disable-next-line:no-unused-variable
20+
ElementRef, Component, ContentChild, ViewChild, TemplateRef
2021
} from '@angular/core';
21-
import {CdkStep} from './step';
2222
import {LEFT_ARROW, RIGHT_ARROW, ENTER, SPACE} from '../keyboard/keycodes';
2323
import {coerceNumberProperty} from '../coercion/number-property';
24+
import {CdkStepLabel} from './step-label';
2425

2526
/** Used to generate unique ID for each stepper component. */
2627
let nextId = 0;
@@ -33,12 +34,38 @@ export class CdkStepperSelectionEvent {
3334
/** The index of the step that was previously selected. */
3435
oldIndex: number;
3536

36-
/** The step component that is selected ruing this change event. */
37-
step: CdkStep;
37+
/** The new step component that is selected ruing this change event. */
38+
newStep: CdkStep;
39+
40+
/** The step component that was previously selected. */
41+
oldStep: CdkStep;
42+
}
43+
44+
@Component({
45+
selector: 'cdk-step',
46+
templateUrl: 'step.html',
47+
})
48+
export class CdkStep {
49+
/** Template for step label if it exists. */
50+
@ContentChild(CdkStepLabel) stepLabel: CdkStepLabel;
51+
52+
/** Template for step content. */
53+
@ViewChild(TemplateRef) content: TemplateRef<any>;
54+
55+
/** Label of the step. */
56+
@Input()
57+
label: string;
58+
59+
constructor(private _stepper: CdkStepper) { }
60+
61+
/** Selects this step component. */
62+
select(): void {
63+
this._stepper.select(this);
64+
}
3865
}
3966

4067
@Directive({
41-
selector: 'cdk-stepper',
68+
selector: 'cdkStepper',
4269
host: {
4370
'(focus)': '_setStepfocused()',
4471
'(keydown)': '_onKeydown($event)',
@@ -62,9 +89,10 @@ export class CdkStepper {
6289
/** Event emitted when the selected step has changed. */
6390
@Output() selectionChange = new EventEmitter<CdkStepperSelectionEvent>();
6491

65-
/** The index of the step that the focus is currently on. */
92+
/** The index of the step that the focus can be set. */
6693
_focusIndex: number = 0;
6794

95+
/** Used to track unique ID for each stepper component. */
6896
private _groupId: number;
6997

7098
constructor() {
@@ -74,28 +102,25 @@ export class CdkStepper {
74102
/** Selects and focuses the provided step. */
75103
select(step: CdkStep | number): void {
76104
if (typeof step == 'number') {
77-
this.selectionChange.emit(this._createStepperSelectionEvent(step, this._selectedIndex));
105+
this._emitStepperSelectionEvent(step, this._selectedIndex);
78106
} else {
79107
let stepsArray = this._steps.toArray();
80-
this.selectionChange.emit(
81-
this._createStepperSelectionEvent(stepsArray.indexOf(step), this._selectedIndex));
108+
this._emitStepperSelectionEvent(stepsArray.indexOf(step), this._selectedIndex);
82109
}
83110
this._setStepFocused(this._selectedIndex);
84111
}
85112

86113
/** Selects and focuses the next step in list. */
87114
next(): void {
88115
if (this._selectedIndex == this._steps.length - 1) { return; }
89-
this.selectionChange.emit(
90-
this._createStepperSelectionEvent(this._selectedIndex + 1, this._selectedIndex));
116+
this._emitStepperSelectionEvent(this._selectedIndex + 1, this._selectedIndex);
91117
this._setStepFocused(this._selectedIndex);
92118
}
93119

94120
/** Selects and focuses the previous step in list. */
95121
previous(): void {
96122
if (this._selectedIndex == 0) { return; }
97-
this.selectionChange.emit(
98-
this._createStepperSelectionEvent(this._selectedIndex - 1, this._selectedIndex));
123+
this._emitStepperSelectionEvent(this._selectedIndex - 1, this._selectedIndex);
99124
this._setStepFocused(this._selectedIndex);
100125
}
101126

@@ -104,36 +129,41 @@ export class CdkStepper {
104129
return `mat-step-label-${this._groupId}-${i}`;
105130
}
106131

107-
/** Returns a unique id for each step content element. */
132+
/** Returns nique id for each step content element. */
108133
_getStepContentId(i: number): string {
109134
return `mat-step-content-${this._groupId}-${i}`;
110135
}
111136

112-
private _createStepperSelectionEvent(newIndex: number,
113-
oldIndex: number): CdkStepperSelectionEvent {
137+
private _emitStepperSelectionEvent(newIndex: number,
138+
oldIndex: number): void {
114139
this._selectedIndex = newIndex;
115140
const event = new CdkStepperSelectionEvent();
116141
event.newIndex = newIndex;
117142
event.oldIndex = oldIndex;
118-
event.step = this._steps.toArray()[this._selectedIndex];
119-
return event;
143+
event.oldStep = this._steps.toArray()[oldIndex];
144+
event.newStep = this._steps.toArray()[this._selectedIndex];
145+
this.selectionChange.emit(event);
120146
}
121147

122148
_onKeydown(event: KeyboardEvent) {
123149
switch (event.keyCode) {
124150
case RIGHT_ARROW:
125151
if (this._focusIndex != this._steps.length - 1) {
126152
this._setStepFocused(this._focusIndex + 1);
153+
} else {
154+
this._setStepFocused(0);
127155
}
128156
break;
129157
case LEFT_ARROW:
130158
if (this._focusIndex != 0) {
131159
this._setStepFocused(this._focusIndex - 1);
160+
} else {
161+
this._setStepFocused(this._steps.length - 1);
132162
}
133163
break;
134164
case SPACE:
135165
case ENTER:
136-
this._createStepperSelectionEvent(this._focusIndex, this._selectedIndex);
166+
this._emitStepperSelectionEvent(this._focusIndex, this._selectedIndex);
137167
break;
138168
default:
139169
return;

src/lib/stepper/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,20 @@ import {MdButtonModule} from '../button/index';
1313
import {MdHorizontalStepper} from './stepper-horizontal';
1414
import {MdVerticalStepper} from './stepper-vertical';
1515
import {MdStep} from './step';
16+
import {MdStepper} from './stepper';
1617
import {CdkStepperModule} from '@angular/cdk';
1718
import {MdCommonModule} from '../core';
1819
import {MdStepLabel} from './step-label';
1920

2021
@NgModule({
2122
imports: [MdCommonModule, CommonModule, PortalModule, MdButtonModule, CdkStepperModule],
22-
exports: [MdCommonModule, MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel],
23-
declarations: [MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel]
23+
exports: [MdCommonModule, MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper],
24+
declarations: [MdHorizontalStepper, MdVerticalStepper, MdStep, MdStepLabel, MdStepper],
2425
})
2526
export class MdStepperModule {}
2627

2728
export * from './stepper-horizontal';
2829
export * from './stepper-vertical';
2930
export * from './step';
3031
export * from './step-label';
32+
export * from './stepper';

src/lib/stepper/step-label.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {CdkStepLabel} from '@angular/cdk';
1313
selector: '[md-step-label], [mat-step-label]',
1414
})
1515
export class MdStepLabel extends CdkStepLabel {
16-
constructor(public template: TemplateRef<any>) {
16+
constructor(template: TemplateRef<any>) {
1717
super(template);
1818
}
1919
}

src/lib/stepper/step.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import {Component, ContentChild, TemplateRef, ViewChild} from '@angular/core';
1010
import {CdkStep} from '@angular/cdk';
1111
import {MdStepLabel} from './step-label';
12+
import {MdStepper} from './stepper';
1213

1314
@Component({
1415
moduleId: module.id,
@@ -21,4 +22,8 @@ export class MdStep extends CdkStep {
2122

2223
/** Template inside the MdStep view that contains an <ng-content>. */
2324
@ViewChild(TemplateRef) content: TemplateRef<any>;
25+
26+
constructor(mdStepper: MdStepper) {
27+
super(mdStepper);
28+
}
2429
}
Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
<div class="mat-stepper-container">
2-
<div *ngFor="let step of _steps; let i = index" class="mat-step" role="tablist">
3-
<div #stepHeader class="mat-step-header" role="tab"
4-
[id]="_getStepLabelId(i)"
5-
[attr.aria-controls]="_getStepContentId(i)"
6-
[attr.aria-selected]="selectedIndex == i"
7-
[tabIndex]="_focusIndex == i ? 0 : -1"
8-
(click)="select(step)"
9-
(keydown)="_onKeydown($event)">
10-
<div *ngIf="step.active" class="active-step">{{i + 1}}</div>
11-
<div *ngIf="!step.active" class="inactive-step">{{i + 1}}</div>
2+
<div *ngFor="let step of _steps; let i = index; let isLast = last"
3+
class="mat-step" role="tablist">
4+
<div #stepHeader class="mat-step-header" role="tab"
5+
[id]="_getStepLabelId(i)"
6+
[attr.aria-controls]="_getStepContentId(i)"
7+
[attr.aria-selected]="selectedIndex == i"
8+
[tabIndex]="_focusIndex == i ? 0 : -1"
9+
(click)="step.select()"
10+
(keydown)="_onKeydown($event)">
11+
<div [ngClass]="{'active-step' : step.active, 'inactive-step' : !step.active}">
12+
{{i + 1}}
13+
</div>
1214

13-
<div class="mat-step-label">
14-
<!-- If there is a label template, use it. -->
15-
<ng-template [ngIf]="step.stepLabel">
16-
<ng-container [ngTemplateOutlet]="step.stepLabel.template"></ng-container>
17-
</ng-template>
18-
<!-- It there is no label template, fall back to the text label. -->
19-
<ng-template [ngIf]="!step.stepLabel">
20-
<div>{{step.label}}</div>
21-
</ng-template>
22-
</div>
15+
<div class="mat-step-label">
16+
<!-- If there is a label template, use it. -->
17+
<ng-container *ngIf="step.stepLabel"[ngTemplateOutlet]="step.stepLabel.template">
18+
</ng-container>
19+
<!-- It there is no label template, fall back to the text label. -->
20+
<div *ngIf="!step.stepLabel">{{step.label}}</div>
21+
</div>
2322

24-
</ng-template>
25-
<div *ngIf="!step._isLast" class="connector-line"></div>
23+
</ng-template>
24+
<div *ngIf="!isLast" class="connector-line"></div>
2625
</div>
27-
</div>
28-
<div *ngFor="let step of _steps; let i = index"
29-
class="mat-stepper-horizontal" role="tabpanel"
30-
[id]="_getStepContentId(i)"
31-
[attr.aria-labelledby]="_getStepLabelId(i)"
32-
[attr.aria-expanded]="selectedIndex == i">
26+
</div>
27+
<div *ngFor="let step of _steps; let i = index"
28+
class="mat-stepper-horizontal" role="tabpanel"
29+
[id]="_getStepContentId(i)"
30+
[attr.aria-labelledby]="_getStepLabelId(i)"
31+
[attr.aria-expanded]="selectedIndex == i">
3332
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
34-
</div>
35-
<div>
33+
</div>
34+
<div>
3635
<button md-button (click)="previous()">Back</button>
3736
<button md-button (click)="next()">Next</button>
37+
</div>
3838
</div>

src/lib/stepper/stepper-horizontal.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Component, ContentChildren, QueryList} from '@angular/core';
9+
import {Component, ContentChildren, forwardRef, QueryList} from '@angular/core';
1010
import {MdStep} from './step';
11-
import {CdkStepper} from '@angular/cdk';
11+
import {MdStepper} from './stepper';
1212

1313
@Component({
1414
moduleId: module.id,
1515
selector: 'md-horizontal-stepper, mat-horizontal-stepper',
1616
templateUrl: 'stepper-horizontal.html',
1717
styleUrls: ['stepper.scss'],
1818
inputs: ['selectedIndex'],
19+
providers: [{ provide: MdStepper, useExisting: forwardRef(() => MdHorizontalStepper) }]
1920
})
20-
export class MdHorizontalStepper extends CdkStepper {
21+
export class MdHorizontalStepper extends MdStepper {
22+
/** Steps that the horizontal stepper holds. */
2123
@ContentChildren(MdStep) _steps: QueryList<MdStep>;
2224
}

0 commit comments

Comments
 (0)