-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(stepper): Merge initial prototype of stepper into the upstream stepper branch. #5742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 23 commits
33c6299
366b4c3
d8e3075
f94d966
23bfd0c
63d141e
471df0f
1d6ebb2
03e5849
c1cb73f
e90c839
992a07a
9e5720a
66bec54
617c270
42c1f14
b879732
f89b8bc
a529bf3
34ac69e
aee0abf
fcb94ff
5e9085f
f417435
36ad2cc
836c0ac
78c9eb5
26f73f9
a56e57f
3c1f4fd
0c3f7c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* @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 {NgModule} from '@angular/core'; | ||
import {CdkStepper} from './stepper'; | ||
import {CommonModule} from '@angular/common'; | ||
import {CdkStep} from './step'; | ||
import {CdkStepLabel} from './step-label'; | ||
import {PortalModule} from '../portal'; | ||
@NgModule({ | ||
imports: [CommonModule, PortalModule], | ||
exports: [CdkStep, CdkStepper, CdkStepLabel], | ||
declarations: [CdkStep, CdkStepper, CdkStepLabel] | ||
}) | ||
export class CdkStepperModule {} | ||
|
||
export * from './stepper'; | ||
export * from './step'; | ||
export * from './step-label'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* @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, TemplateRef, ViewContainerRef} from '@angular/core'; | ||
import {TemplatePortalDirective} from '../portal'; | ||
@Directive({ | ||
selector: '[cdk-step-label]', | ||
}) | ||
export class CdkStepLabel extends TemplatePortalDirective { | ||
constructor(templateRef: TemplateRef<any>, viewContainerRef: ViewContainerRef) { | ||
super(templateRef, viewContainerRef); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<ng-template><ng-content></ng-content></ng-template> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* @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, ContentChild, Directive, Input, OnInit, TemplateRef, ViewChild, | ||
ViewContainerRef | ||
} from '@angular/core'; | ||
import {CdkStepLabel} from './step-label'; | ||
|
||
@Component({ | ||
selector: '[cdk-step]', | ||
templateUrl: 'step.html', | ||
}) | ||
export class CdkStep { | ||
@ContentChild(CdkStepLabel) stepLabel: CdkStepLabel; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. \n (and comment what these are) |
||
@ViewChild(TemplateRef) content: TemplateRef<any>; | ||
|
||
/** Label of the step. */ | ||
@Input() | ||
label: string; | ||
|
||
/** Whether the step is optional or not. */ | ||
@Input() optional: boolean = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. split into setter & getter, and use |
||
|
||
/** Whether the step is editable or not. */ | ||
@Input() editable: boolean = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
|
||
/** Whether the step is the last one in the list. */ | ||
isLast: boolean = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this something we want to be part of the public API? (I asusme probably not, but we can't make it private since its referenced in a template file, so instead check to |
||
|
||
// /** Whether the step is active. */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no longer needed? |
||
// get active() { return this._active; } | ||
// set active(value: boolean) { | ||
// this._active = value; | ||
// } | ||
// private _active: boolean = false; | ||
|
||
/** Whether the step has been selected. */ | ||
get selected(): boolean { return this._selected; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are |
||
set selected(value: boolean) { | ||
this._selected = value; | ||
} | ||
private _selected: boolean = false; | ||
|
||
/** Whether the step has been completed. */ | ||
get completed() { return this._completed; } | ||
set completed(value: boolean) { | ||
this._completed = value; | ||
} | ||
private _completed: boolean = false; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/** | ||
* @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, ContentChildren, EventEmitter, Input, Output, QueryList, OnInit, | ||
AfterViewChecked, AfterViewInit, Directive, ElementRef, ViewChild, ViewChildren | ||
} from '@angular/core'; | ||
import {CdkStep} from './step'; | ||
import {Observable} from 'rxjs/Observable'; | ||
import {map} from 'rxjs/operator/map'; | ||
import {LEFT_ARROW, RIGHT_ARROW, ENTER, TAB} from '../keyboard/keycodes'; | ||
|
||
/** Used to generate unique ID for each stepper component. */ | ||
let nextId = 0; | ||
|
||
/** Change event emitted on focus or selection changes. */ | ||
export class CdkStepEvent { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
index: number; | ||
step: CdkStep; | ||
} | ||
|
||
@Directive({ | ||
selector: '[cdkStepper]', | ||
host: { | ||
'(keydown)': '_onKeydown($event)' | ||
}, | ||
}) | ||
export class CdkStepper { | ||
@ContentChildren(CdkStep) _steps: QueryList<CdkStep>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment what these are |
||
|
||
@ViewChildren('stepHeader') _stepHeader: QueryList<ElementRef>; | ||
|
||
/** The index of the currently selected step. */ | ||
@Input() | ||
set selectedIndex(value: number) { | ||
this._selectedIndex = value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. coerceNumberProperty(value) |
||
} | ||
get selectedIndex(): number { return this._selectedIndex; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: getter first, then setter |
||
private _selectedIndex: number; | ||
|
||
/** Optional input to support both linear and non-linear stepper component. */ | ||
@Input() linear: boolean = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove for now (add back if/when you hook it up to something) |
||
|
||
/** Output to enable support for two-way binding on `[(selectedIndex)]` */ | ||
@Output() get selectedIndexChange(): Observable<number> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't the index part of the |
||
return map.call(this.stepEvent, event => event.index); | ||
} | ||
|
||
// @Output() get focusIndexChange(): Observable<number> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no longer needed? |
||
// return map.call(this.focusChange, event => event.index); | ||
// } | ||
|
||
/** Event emitted when the selected step has changed. */ | ||
@Output() stepEvent = new EventEmitter<CdkStepEvent>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would call this |
||
|
||
/** Event emitted when the focused step has changed. */ | ||
@Output() focusChange = new EventEmitter<CdkStepEvent>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need this? I would think the developer can manage this themselves with |
||
|
||
/** The step that is currently selected. */ | ||
get selectedStep(): CdkStep { | ||
return this._steps.toArray()[this._selectedIndex]; | ||
} | ||
private _selectedStep: CdkStep; | ||
|
||
/** The index of the step that the focus is currently on. */ | ||
get focusIndex(): number {return this._focusIndex; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need a public API for this? seems like an internal detail |
||
private _focusIndex: number = 0; | ||
|
||
private _groupId: number; | ||
|
||
constructor() { | ||
this._groupId = nextId++; | ||
} | ||
|
||
/** Selects and focuses the provided step. */ | ||
selectStep(step: CdkStep): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this similar to what tabs does? From an API perspective I would rather There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it seems like tabs doesn't have a separate method for selecting. It just updates and sets the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm saying that as an API I prefer: |
||
let stepsArray = this._steps.toArray(); | ||
this._selectedIndex = stepsArray.indexOf(step); | ||
this.stepEvent.emit(this._emitStepEvent(this._selectedIndex)); | ||
this._focusIndex = this._selectedIndex; | ||
this._setStepFocus(); | ||
} | ||
|
||
/** Selects and focuses the next step in list. */ | ||
nextStep(): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would just call it |
||
if (this._selectedIndex == this._steps.length - 1) { return; } | ||
this._selectedIndex++; | ||
this.stepEvent.emit(this._emitStepEvent(this._selectedIndex)); | ||
this._focusIndex = this._selectedIndex; | ||
this._setStepFocus(); | ||
} | ||
|
||
/** Selects and focuses the previous step in list. */ | ||
previousStep(): void { | ||
if (this._selectedIndex == 0) { return; } | ||
this._selectedIndex--; | ||
this.stepEvent.emit(this._emitStepEvent(this._selectedIndex)); | ||
this._focusIndex = this._selectedIndex; | ||
this._setStepFocus(); | ||
} | ||
|
||
/** Returns a unique id for each step label element. */ | ||
_getStepLabelId(i: number): string { | ||
return `mat-step-label-${this._groupId}-${i}`; | ||
} | ||
|
||
/** Returns a unique id for each step content element. */ | ||
_getStepContentId(i: number): string { | ||
return `mat-step-content-${this._groupId}-${i}`; | ||
} | ||
|
||
private _emitStepEvent(index: number): CdkStepEvent { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. name is deceptive since this method doesn't actually cause an emit |
||
const event = new CdkStepEvent(); | ||
event.index = index; | ||
event.step = this._steps.toArray()[this._selectedIndex]; | ||
this._selectedStep = event.step; | ||
return event; | ||
} | ||
|
||
_onKeydown(event: KeyboardEvent) { | ||
switch (event.keyCode) { | ||
case RIGHT_ARROW: | ||
if (this._focusIndex != this._steps.length - 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wouldn't be equivalent to |
||
this._focusIndex++; | ||
this._setStepFocus(); | ||
} | ||
break; | ||
case LEFT_ARROW: | ||
if (this._focusIndex != 0) { | ||
this._focusIndex--; | ||
this._setStepFocus(); | ||
} | ||
break; | ||
case ENTER: | ||
this._selectedIndex = this._focusIndex; | ||
this._emitStepEvent(this._selectedIndex); | ||
break; | ||
} | ||
if (event.keyCode != TAB) { | ||
event.preventDefault(); | ||
} | ||
} | ||
|
||
_setStepFocus() { | ||
this._stepHeader.toArray()[this._focusIndex].nativeElement.focus(); | ||
this.focusChange.emit(this._emitStepEvent(this._selectedIndex)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,7 +67,8 @@ export class DemoApp { | |
{name: 'Tooltip', route: 'tooltip'}, | ||
{name: 'Platform', route: 'platform'}, | ||
{name: 'Style', route: 'style'}, | ||
{name: 'Typography', route: 'typography'} | ||
{name: 'Typography', route: 'typography'}, | ||
{name: 'Stepper', route: 'stepper'} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you place this in the list alphabetically |
||
]; | ||
|
||
constructor(private _element: ElementRef) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<h2>Horizontal Stepper Demo</h2> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are ok for now, but we should eventually add examples that are more like what a developer would actually want to create. |
||
<mat-horizontal-stepper [(selectedIndex)]="horizontalActiveIndex"> | ||
<mat-step *ngFor="let step of steps" [label]="step.label"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: indent 2 |
||
<md-input-container> | ||
<input mdInput placeholder="Answer" [(ngModel)]="step.content"> | ||
</md-input-container> | ||
</mat-step> | ||
</mat-horizontal-stepper> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import {Component} from '@angular/core'; | ||
|
||
@Component({ | ||
moduleId: module.id, | ||
selector: 'stepper-demo', | ||
templateUrl: 'stepper-demo.html', | ||
styleUrls: ['stepper-demo.scss'], | ||
}) | ||
export class StepperDemo { | ||
verticalActiveIndex = 0; | ||
horizontalActiveIndex = 0; | ||
labelTemplateIndex = 0; | ||
steps = [ | ||
{label: 'Confirm your name', content: 'Last name, First name.'}, | ||
{label: 'Confirm your contact information', content: '123-456-7890'}, | ||
{label: 'Confirm your address', content: '1600 Amphitheater Pkwy MTV'}, | ||
{label: 'You are now done', content: 'Finished!'} | ||
]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can do this the same way you did
CdkStep
(just aTemplateRef
andngTemplateOutlet
tabs used the portal stuff because it was written beforengTemplateOutlet
was a thing