Skip to content

Commit

Permalink
feat(stepper): Merge initial prototype of stepper into the upstream s…
Browse files Browse the repository at this point in the history
…tepper branch. (#5742)

* Prototyping

* Further work

* Further prototyping

* Further prototyping

* Further work

* Adding event emitters

* Adding "selectedIndex" attribute to stepper and working on TemplateOulet.

* Prototyping

* Further work

* Further prototyping

* Further prototyping

* Further work

* Adding event emitters

* Template rendering and selectIndex control done.

* Work in progress for accessibility

* Added functionalities based on the tentative API doc.

* Refactor code for cdk-stepper and cdk-step

* Add support for templated label

* Added support for keyboard events and focus changes for accessibility.

* Updated vertical stepper + added comments

* Fix package-lock.json

* Fix indention

* Changes made based on the review

* Changes based on review - event properties, selectors, SPACE support, etc. + demo

* Add select() for step component + refactor to avoid circular dependency + support cycling using arrow keys

* API change based on review

* Minor code clean up based on review.

* Several name changes, etc based on review

* Add to compatibility mode list and refactor to avoid circular dependency
  • Loading branch information
jwshinjwshin authored Jul 21, 2017
1 parent a34787d commit 4438480
Show file tree
Hide file tree
Showing 23 changed files with 493 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/cdk/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from './portal/index';
export * from './rxjs/index';
export * from './observe-content/index';
export * from './keyboard/index';
export * from './stepper/index';
22 changes: 22 additions & 0 deletions src/cdk/stepper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @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, CdkStep} from './stepper';
import {CommonModule} from '@angular/common';
import {CdkStepLabel} from './step-label';

@NgModule({
imports: [CommonModule],
exports: [CdkStep, CdkStepper, CdkStepLabel],
declarations: [CdkStep, CdkStepper, CdkStepLabel]
})
export class CdkStepperModule {}

export * from './stepper';
export * from './step-label';
16 changes: 16 additions & 0 deletions src/cdk/stepper/step-label.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @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} from '@angular/core';

@Directive({
selector: '[cdkStepLabel]',
})
export class CdkStepLabel {
constructor(public template: TemplateRef<any>) { }
}
1 change: 1 addition & 0 deletions src/cdk/stepper/step.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<ng-template><ng-content></ng-content></ng-template>
169 changes: 169 additions & 0 deletions src/cdk/stepper/stepper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* @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 {
ContentChildren,
EventEmitter,
Input,
Output,
QueryList,
Directive,
// This import is only used to define a generic type. The current TypeScript version incorrectly
// considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953)
// tslint:disable-next-line:no-unused-variable
ElementRef,
Component,
ContentChild,
ViewChild,
TemplateRef
} from '@angular/core';
import {LEFT_ARROW, RIGHT_ARROW, ENTER, SPACE} from '../keyboard/keycodes';
import {CdkStepLabel} from './step-label';

/** Used to generate unique ID for each stepper component. */
let nextId = 0;

/** Change event emitted on selection changes. */
export class CdkStepperSelectionEvent {
/** Index of the step now selected. */
selectedIndex: number;

/** Index of the step previously selected. */
previouslySelectedIndex: number;

/** The step instance now selected. */
selectedStep: CdkStep;

/** The step instance previously selected. */
previouslySelectedStep: CdkStep;
}

@Component({
selector: 'cdk-step',
templateUrl: 'step.html',
})
export class CdkStep {
/** Template for step label if it exists. */
@ContentChild(CdkStepLabel) stepLabel: CdkStepLabel;

/** Template for step content. */
@ViewChild(TemplateRef) content: TemplateRef<any>;

/** Label of the step. */
@Input()
label: string;

constructor(private _stepper: CdkStepper) { }

/** Selects this step component. */
select(): void {
this._stepper.selected = this;
}
}

@Directive({
selector: 'cdk-stepper',
host: {
'(focus)': '_focusStep()',
'(keydown)': '_onKeydown($event)',
},
})
export class CdkStepper {
/** The list of step components that the stepper is holding. */
@ContentChildren(CdkStep) _steps: QueryList<CdkStep>;

/** The list of step headers of the steps in the stepper. */
_stepHeader: QueryList<ElementRef>;

/** The index of the selected step. */
@Input()
get selectedIndex() { return this._selectedIndex; }
set selectedIndex(index: number) {
if (this._selectedIndex != index) {
this._emitStepperSelectionEvent(index);
this._focusStep(this._selectedIndex);
}
}
private _selectedIndex: number = 0;

/** The step that is selected. */
@Input()
get selected() { return this._steps[this.selectedIndex]; }
set selected(step: CdkStep) {
let index = this._steps.toArray().indexOf(step);
this.selectedIndex = index;
}

/** Event emitted when the selected step has changed. */
@Output() selectionChange = new EventEmitter<CdkStepperSelectionEvent>();

/** The index of the step that the focus can be set. */
_focusIndex: number = 0;

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

constructor() {
this._groupId = nextId++;
}

/** Selects and focuses the next step in list. */
next(): void {
this.selectedIndex = Math.min(this._selectedIndex + 1, this._steps.length - 1);
}

/** Selects and focuses the previous step in list. */
previous(): void {
this.selectedIndex = Math.max(this._selectedIndex - 1, 0);
}

/** Returns a unique id for each step label element. */
_getStepLabelId(i: number): string {
return `mat-step-label-${this._groupId}-${i}`;
}

/** Returns nique id for each step content element. */
_getStepContentId(i: number): string {
return `mat-step-content-${this._groupId}-${i}`;
}

private _emitStepperSelectionEvent(newIndex: number): void {
const stepsArray = this._steps.toArray();
this.selectionChange.emit({
selectedIndex: newIndex,
previouslySelectedIndex: this._selectedIndex,
selectedStep: stepsArray[newIndex],
previouslySelectedStep: stepsArray[this._selectedIndex],
});
this._selectedIndex = newIndex;
}

_onKeydown(event: KeyboardEvent) {
switch (event.keyCode) {
case RIGHT_ARROW:
this._focusStep((this._focusIndex + 1) % this._steps.length);
break;
case LEFT_ARROW:
this._focusStep((this._focusIndex + this._steps.length - 1) % this._steps.length);
break;
case SPACE:
case ENTER:
this._emitStepperSelectionEvent(this._focusIndex);
break;
default:
// Return to avoid calling preventDefault on keys that are not explicitly handled.
return;
}
event.preventDefault();
}

private _focusStep(index: number) {
this._focusIndex = index;
this._stepHeader.toArray()[this._focusIndex].nativeElement.focus();
}
}
8 changes: 6 additions & 2 deletions src/demo-app/demo-app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ import {
MdToolbarModule,
MdTooltipModule,
OverlayContainer,
StyleModule
StyleModule,
MdStepperModule,
} from '@angular/material';
import {CdkTableModule} from '@angular/cdk';
import {TableHeaderDemo} from './table/table-header-demo';
import {StepperDemo} from './stepper/stepper-demo';

/**
* NgModule that includes all Material modules that are required to serve the demo-app.
Expand Down Expand Up @@ -118,7 +120,8 @@ import {TableHeaderDemo} from './table/table-header-demo';
MdTooltipModule,
MdNativeDateModule,
CdkTableModule,
StyleModule
StyleModule,
MdStepperModule,
]
})
export class DemoMaterialModule {}
Expand Down Expand Up @@ -184,6 +187,7 @@ export class DemoMaterialModule {}
PlatformDemo,
TypographyDemo,
ExpansionDemo,
StepperDemo,
],
providers: [
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
Expand Down
1 change: 1 addition & 0 deletions src/demo-app/demo-app/demo-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export class DemoApp {
{name: 'Slider', route: 'slider'},
{name: 'Slide Toggle', route: 'slide-toggle'},
{name: 'Snack Bar', route: 'snack-bar'},
{name: 'Stepper', route: 'stepper'},
{name: 'Table', route: 'table'},
{name: 'Tabs', route: 'tabs'},
{name: 'Toolbar', route: 'toolbar'},
Expand Down
2 changes: 2 additions & 0 deletions src/demo-app/demo-app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {DatepickerDemo} from '../datepicker/datepicker-demo';
import {TableDemo} from '../table/table-demo';
import {TypographyDemo} from '../typography/typography-demo';
import {ExpansionDemo} from '../expansion/expansion-demo';
import {StepperDemo} from '../stepper/stepper-demo';

export const DEMO_APP_ROUTES: Routes = [
{path: '', component: Home},
Expand Down Expand Up @@ -74,4 +75,5 @@ export const DEMO_APP_ROUTES: Routes = [
{path: 'style', component: StyleDemo},
{path: 'typography', component: TypographyDemo},
{path: 'expansion', component: ExpansionDemo},
{path: 'stepper', component: StepperDemo},
];
27 changes: 27 additions & 0 deletions src/demo-app/stepper/stepper-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<h2>Horizontal Stepper Demo</h2>
<md-horizontal-stepper>
<md-step *ngFor="let step of steps" [label]="step.label">
<md-input-container>
<input mdInput placeholder="Answer" [(ngModel)]="step.content">
</md-input-container>
</md-step>
</md-horizontal-stepper>

<h2>Horizontal Stepper Demo with Templated Label</h2>
<md-horizontal-stepper>
<md-step *ngFor="let step of steps">
<ng-template mdStepLabel>{{step.label}}</ng-template>
<md-input-container>
<input mdInput placeholder="Answer" [(ngModel)]="step.content">
</md-input-container>
</md-step>
</md-horizontal-stepper>

<h2>Vertical Stepper Demo</h2>
<md-vertical-stepper>
<md-step *ngFor="let step of steps" [label]="step.label">
<md-input-container>
<input mdInput placeholder="Answer" [(ngModel)]="step.content">
</md-input-container>
</md-step>
</md-vertical-stepper>
Empty file.
16 changes: 16 additions & 0 deletions src/demo-app/stepper/stepper-demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Component} from '@angular/core';

@Component({
moduleId: module.id,
selector: 'stepper-demo',
templateUrl: 'stepper-demo.html',
styleUrls: ['stepper-demo.scss'],
})
export class StepperDemo {
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!'}
];
}
12 changes: 10 additions & 2 deletions src/lib/core/compatibility/compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const MAT_ELEMENTS_SELECTOR = `
[matDialogContent],
[matDialogTitle],
[matLine],
[matStepLabel],
[matTabLabel],
[matTabLink],
[matTabNav],
Expand Down Expand Up @@ -64,6 +65,7 @@ export const MAT_ELEMENTS_SELECTOR = `
mat-grid-tile-header,
mat-header-cell,
mat-hint,
mat-horizontal-stepper,
mat-icon,
mat-list,
mat-list-item,
Expand All @@ -81,10 +83,12 @@ export const MAT_ELEMENTS_SELECTOR = `
mat-sidenav-container,
mat-slider,
mat-spinner,
mat-step,
mat-tab,
mat-table,
mat-tab-group,
mat-toolbar`;
mat-toolbar,
mat-vertical-stepper`;

/** Selector that matches all elements that may have style collisions with AngularJS Material. */
export const MD_ELEMENTS_SELECTOR = `
Expand All @@ -100,6 +104,7 @@ export const MD_ELEMENTS_SELECTOR = `
[mdDialogContent],
[mdDialogTitle],
[mdLine],
[mdStepLabel],
[mdTabLabel],
[mdTabLink],
[mdTabNav],
Expand Down Expand Up @@ -130,6 +135,7 @@ export const MD_ELEMENTS_SELECTOR = `
md-grid-tile-header,
md-header-cell,
md-hint,
md-horizontal-stepper,
md-icon,
md-list,
md-list-item,
Expand All @@ -147,10 +153,12 @@ export const MD_ELEMENTS_SELECTOR = `
md-sidenav-container,
md-slider,
md-spinner,
md-step,
md-tab,
md-table,
md-tab-group,
md-toolbar`;
md-toolbar,
md-vertical-stepper`;

/** Directive that enforces that the `mat-` prefix cannot be used. */
@Directive({selector: MAT_ELEMENTS_SELECTOR})
Expand Down
4 changes: 3 additions & 1 deletion src/lib/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {MdExpansionModule} from './expansion/index';
import {MdTableModule} from './table/index';
import {MdSortModule} from './sort/index';
import {MdPaginatorModule} from './paginator/index';
import {MdStepperModule} from './stepper/index';

const MATERIAL_MODULES = [
MdAutocompleteModule,
Expand Down Expand Up @@ -86,7 +87,8 @@ const MATERIAL_MODULES = [
A11yModule,
PlatformModule,
MdCommonModule,
ObserveContentModule
ObserveContentModule,
MdStepperModule,
];

/** @deprecated */
Expand Down
1 change: 1 addition & 0 deletions src/lib/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ export * from './tabs/index';
export * from './tabs/tab-nav-bar/index';
export * from './toolbar/index';
export * from './tooltip/index';
export * from './stepper/index';
Loading

0 comments on commit 4438480

Please sign in to comment.