Skip to content

Commit

Permalink
fix(stepper): unable to internationalize labels (#7122)
Browse files Browse the repository at this point in the history
crisbeto authored and kara committed Oct 3, 2017

Verified

This commit was signed with the committer’s verified signature.
thaJeztah Sebastiaan van Stijn
1 parent ac70420 commit 6e3bbcb
Showing 7 changed files with 88 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/lib/stepper/public-api.ts
Original file line number Diff line number Diff line change
@@ -11,4 +11,4 @@ export * from './step-label';
export * from './stepper';
export * from './stepper-button';
export * from './step-header';

export * from './stepper-intl';
2 changes: 1 addition & 1 deletion src/lib/stepper/step-header.html
Original file line number Diff line number Diff line change
@@ -15,6 +15,6 @@
<!-- It there is no label template, fall back to the text label. -->
<div class="mat-step-text-label" *ngIf="_stringLabel()">{{label}}</div>

<div class="mat-step-optional" *ngIf="optional">Optional</div>
<div class="mat-step-optional" *ngIf="optional">{{_intl.optionalLabel}}</div>
</div>

20 changes: 18 additions & 2 deletions src/lib/stepper/step-header.ts
Original file line number Diff line number Diff line change
@@ -8,8 +8,18 @@

import {FocusMonitor} from '@angular/cdk/a11y';
import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion';
import {Component, Input, ViewEncapsulation, ElementRef, OnDestroy, Renderer2} from '@angular/core';
import {
Component,
Input,
ViewEncapsulation,
ChangeDetectorRef,
OnDestroy,
ElementRef,
Renderer2,
} from '@angular/core';
import {MatStepLabel} from './step-label';
import {MatStepperIntl} from './stepper-intl';
import {Subscription} from 'rxjs/Subscription';


@Component({
@@ -25,6 +35,8 @@ import {MatStepLabel} from './step-label';
preserveWhitespaces: false,
})
export class MatStepHeader implements OnDestroy {
private _intlSubscription: Subscription;

/** Icon for the given step. */
@Input() icon: string;

@@ -64,13 +76,17 @@ export class MatStepHeader implements OnDestroy {
private _optional: boolean;

constructor(
public _intl: MatStepperIntl,
private _focusMonitor: FocusMonitor,
private _element: ElementRef,
renderer: Renderer2) {
renderer: Renderer2,
changeDetectorRef: ChangeDetectorRef) {
_focusMonitor.monitor(_element.nativeElement, renderer, true);
this._intlSubscription = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
}

ngOnDestroy() {
this._intlSubscription.unsubscribe();
this._focusMonitor.stopMonitoring(this._element.nativeElement);
}

24 changes: 24 additions & 0 deletions src/lib/stepper/stepper-intl.ts
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 {Injectable} from '@angular/core';
import {Subject} from 'rxjs/Subject';


/** Stepper data that is required for internationalization. */
@Injectable()
export class MatStepperIntl {
/**
* Stream that emits whenever the labels here are changed. Use this to notify
* components if the labels have changed after initialization.
*/
changes: Subject<void> = new Subject<void>();

/** Label that is rendered below optional steps. */
optionalLabel = 'Optional';
}
2 changes: 2 additions & 0 deletions src/lib/stepper/stepper-module.ts
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ import {MatStepHeader} from './step-header';
import {MatStepLabel} from './step-label';
import {MatHorizontalStepper, MatStep, MatStepper, MatVerticalStepper} from './stepper';
import {MatStepperNext, MatStepperPrevious} from './stepper-button';
import {MatStepperIntl} from './stepper-intl';


@NgModule({
@@ -44,5 +45,6 @@ import {MatStepperNext, MatStepperPrevious} from './stepper-button';
],
declarations: [MatHorizontalStepper, MatVerticalStepper, MatStep, MatStepLabel, MatStepper,
MatStepperNext, MatStepperPrevious, MatStepHeader],
providers: [MatStepperIntl],
})
export class MatStepperModule {}
34 changes: 24 additions & 10 deletions src/lib/stepper/stepper.md
Original file line number Diff line number Diff line change
@@ -7,8 +7,8 @@ that drives a stepped workflow. Material stepper extends the CDK stepper and has
styling.

### Stepper variants
There are two stepper components: `mat-horizontal-stepper` and `mat-vertical-stepper`. They
can be used the same way. The only difference is the orientation of stepper.
There are two stepper components: `mat-horizontal-stepper` and `mat-vertical-stepper`. They
can be used the same way. The only difference is the orientation of stepper.
`mat-horizontal-stepper` selector can be used to create a horizontal stepper, and
`mat-vertical-stepper` can be used to create a vertical stepper. `mat-step` components need to be
placed inside either one of the two stepper components.
@@ -26,7 +26,7 @@ If a step's label is only text, then the `label` attribute can be used.
</mat-vertical-stepper>
```

For more complex labels, add a template with the `matStepLabel` directive inside the
For more complex labels, add a template with the `matStepLabel` directive inside the
`mat-step`.
```html
<mat-vertical-stepper>
@@ -49,22 +49,22 @@ There are two button directives to support navigation between different steps:
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
</mat-horizontal-stepper>
</mat-horizontal-stepper>
```

### Linear stepper
The `linear` attribute can be set on `mat-horizontal-stepper` and `mat-vertical-stepper` to create
a linear stepper that requires the user to complete previous steps before proceeding
to following steps. For each `mat-step`, the `stepControl` attribute can be set to the top level
`AbstractControl` that is used to check the validity of the step.
`AbstractControl` that is used to check the validity of the step.

There are two possible approaches. One is using a single form for stepper, and the other is
using a different form for each step.

#### Using a single form
When using a single form for the stepper, `matStepperPrevious` and `matStepperNext` have to be
set to `type="button"` in order to prevent submission of the form before all steps
are completed.
are completed.

```html
<form [formGroup]="formGroup">
@@ -83,7 +83,7 @@ are completed.
</div>
</mat-step>
...
</mat-horizontal-stepper>
</mat-horizontal-stepper>
</form>
```

@@ -106,11 +106,11 @@ are completed.

#### Optional step
If completion of a step in linear stepper is not required, then the `optional` attribute can be set
on `mat-step`.
on `mat-step`.

#### Editable step
By default, steps are editable, which means users can return to previously completed steps and
edit their responses. `editable="true"` can be set on `mat-step` to change the default.
edit their responses. `editable="true"` can be set on `mat-step` to change the default.

#### Completed step
By default, the `completed` attribute of a step returns `true` if the step is valid (in case of
@@ -124,11 +124,25 @@ this default `completed` behavior by setting the `completed` attribute as needed
- <kbd>TAB</kbd>: Focuses the next tabbable element
- <kbd>TAB</kbd>+<kbd>SHIFT</kbd>: Focuses the previous tabbable element

### Localizing labels
Labels used by the stepper are provided through `MatStepperIntl`. Localization of these messages
can be done by providing a subclass with translated values in your application root module.

```ts
@NgModule({
imports: [MatStepperModule],
providers: [
{provide: MatStepperIntl, useClass: MyIntl},
],
})
export class MyApp {}
```

### Accessibility
The stepper is treated as a tabbed view for accessibility purposes, so it is given
`role="tablist"` by default. The header of step that can be clicked to select the step
is given `role="tab"`, and the content that can be expanded upon selection is given
`role="tabpanel"`. `aria-selected` attribute of step header and `aria-expanded` attribute of
step content is automatically set based on step selection change.

The stepper and each step should be given a meaningful label via `aria-label` or `aria-labelledby`.
The stepper and each step should be given a meaningful label via `aria-label` or `aria-labelledby`.
20 changes: 18 additions & 2 deletions src/lib/stepper/stepper.spec.ts
Original file line number Diff line number Diff line change
@@ -2,13 +2,14 @@ import {Directionality} from '@angular/cdk/bidi';
import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
import {dispatchKeyboardEvent} from '@angular/cdk/testing';
import {Component, DebugElement} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {MatStepperModule} from './index';
import {MatHorizontalStepper, MatStep, MatStepper, MatVerticalStepper} from './stepper';
import {MatStepperNext, MatStepperPrevious} from './stepper-button';
import {MatStepperIntl} from './stepper-intl';

const VALID_REGEX = /valid/;

@@ -95,6 +96,21 @@ describe('MatHorizontalStepper', () => {
it('should set done icon if step is not editable and is completed', () => {
assertCorrectStepIcon(fixture, false, 'done');
});

it('should re-render when the i18n labels change',
inject([MatStepperIntl], (intl: MatStepperIntl) => {
const header = fixture.debugElement.queryAll(By.css('mat-step-header'))[2].nativeElement;
const optionalLabel = header.querySelector('.mat-step-optional');

expect(optionalLabel).toBeTruthy();
expect(optionalLabel.textContent).toBe('Optional');

intl.optionalLabel = 'Valgfri';
intl.changes.next();
fixture.detectChanges();

expect(optionalLabel.textContent).toBe('Valgfri');
}));
});

describe('RTL', () => {
@@ -686,7 +702,7 @@ function assertCorrectStepIcon(fixture: ComponentFixture<any>,
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step [label]="inputLabel">
<mat-step [label]="inputLabel" optional>
Content 3
<div>
<button mat-button matStepperPrevious>Back</button>

0 comments on commit 6e3bbcb

Please sign in to comment.