diff --git a/src/demo-app/toolbar/toolbar-demo.html b/src/demo-app/toolbar/toolbar-demo.html
index a7a434c97ded..fa7f900c945d 100644
--- a/src/demo-app/toolbar/toolbar-demo.html
+++ b/src/demo-app/toolbar/toolbar-demo.html
@@ -35,19 +35,19 @@
- Custom Toolbar
-
- Second Line
-
+ First Row
+ Second Row
- Custom Toolbar
+
+ First Row
+
- Second Line
+ Second Row
@@ -55,7 +55,7 @@
- Third Line
+ Third Row
@@ -64,5 +64,4 @@
-
-
\ No newline at end of file
+
diff --git a/src/lib/toolbar/toolbar-module.ts b/src/lib/toolbar/toolbar-module.ts
index b974fd224417..fca82c739af9 100644
--- a/src/lib/toolbar/toolbar-module.ts
+++ b/src/lib/toolbar/toolbar-module.ts
@@ -8,11 +8,11 @@
import {NgModule} from '@angular/core';
import {MatCommonModule} from '@angular/material/core';
+import {PlatformModule} from '@angular/cdk/platform';
import {MatToolbar, MatToolbarRow} from './toolbar';
-
@NgModule({
- imports: [MatCommonModule],
+ imports: [MatCommonModule, PlatformModule],
exports: [MatToolbar, MatToolbarRow, MatCommonModule],
declarations: [MatToolbar, MatToolbarRow],
})
diff --git a/src/lib/toolbar/toolbar.html b/src/lib/toolbar/toolbar.html
index a9f577369010..f01ae835ce04 100644
--- a/src/lib/toolbar/toolbar.html
+++ b/src/lib/toolbar/toolbar.html
@@ -1,6 +1,2 @@
-
-
-
-
-
-
+
+
diff --git a/src/lib/toolbar/toolbar.md b/src/lib/toolbar/toolbar.md
index c6bebe838470..3cab5742722c 100644
--- a/src/lib/toolbar/toolbar.md
+++ b/src/lib/toolbar/toolbar.md
@@ -2,25 +2,38 @@
-### Multiple rows
-Toolbars can have multiple rows using `` elements. Any content outside of an
-`` element are automatically placed inside of one at the beginning of the toolbar.
-Each toolbar row is a `display: flex` container.
+### Single row
+
+In the most situations, a toolbar will be placed at the top of your application and will only
+have a single row that includes the title of your application.
```html
- First Row
-
+ My Application
+
+```
+
+### Multiple rows
+
+The Material Design specifications describe that toolbars can also have multiple rows. Creating
+toolbars with multiple rows in Angular Material can be done by placing `` elements
+inside of a ``.
+
+```html
+
- Second Row
+ First Row
- Third Row
+ Second Row
```
+**Note**: Placing content outside of a `` when multiple rows are specified is not
+supported.
+
### Positioning toolbar content
The toolbar does not perform any positioning of its content. This gives the user full power to
position the content as it suits their application.
diff --git a/src/lib/toolbar/toolbar.scss b/src/lib/toolbar/toolbar.scss
index 87becbfa16e3..e6b4cb21650b 100644
--- a/src/lib/toolbar/toolbar.scss
+++ b/src/lib/toolbar/toolbar.scss
@@ -4,39 +4,39 @@ $mat-toolbar-height-desktop: 64px !default;
$mat-toolbar-height-mobile-portrait: 56px !default;
$mat-toolbar-height-mobile-landscape: 48px !default;
-$mat-toolbar-padding: 16px !default;
+$mat-toolbar-row-padding: 16px !default;
@mixin mat-toolbar-height($height) {
- .mat-toolbar {
+ .mat-toolbar-multiple-rows {
min-height: $height;
}
- .mat-toolbar-row {
+ .mat-toolbar-row, .mat-toolbar-single-row {
height: $height;
}
}
-.mat-toolbar {
+.mat-toolbar-row, .mat-toolbar-single-row {
display: flex;
box-sizing: border-box;
- width: 100%;
- padding: 0 $mat-toolbar-padding;
- flex-direction: column;
- .mat-toolbar-row {
- display: flex;
- box-sizing: border-box;
+ padding: 0 $mat-toolbar-row-padding;
+ width: 100%;
- width: 100%;
+ // Flexbox Vertical Alignment
+ flex-direction: row;
+ align-items: center;
- // Flexbox Vertical Alignment
- flex-direction: row;
- align-items: center;
+ // Per Material specs a toolbar cannot have multiple lines inside of a single row.
+ // Disable text wrapping inside of the toolbar. Developers are still able to overwrite it.
+ white-space: nowrap;
+}
- // Per Material specs a toolbar cannot have multiple lines inside of a single row.
- // Disable text wrapping inside of the toolbar. Developers are still able to overwrite it.
- white-space: nowrap;
- }
+.mat-toolbar-multiple-rows {
+ display: flex;
+ box-sizing: border-box;
+ flex-direction: column;
+ width: 100%;
}
// Set the default height for the toolbar.
diff --git a/src/lib/toolbar/toolbar.spec.ts b/src/lib/toolbar/toolbar.spec.ts
index bfe43e7d0c2c..86faca6832e3 100644
--- a/src/lib/toolbar/toolbar.spec.ts
+++ b/src/lib/toolbar/toolbar.spec.ts
@@ -3,55 +3,115 @@ import {TestBed, async, ComponentFixture} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {MatToolbarModule} from './index';
-
describe('MatToolbar', () => {
- let fixture: ComponentFixture;
- let testComponent: TestApp;
- let toolbarElement: HTMLElement;
-
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatToolbarModule],
- declarations: [TestApp],
+ declarations: [ToolbarSingleRow, ToolbarMultipleRows, ToolbarMixedRowModes],
});
TestBed.compileComponents();
}));
- beforeEach(() => {
- fixture = TestBed.createComponent(TestApp);
- testComponent = fixture.debugElement.componentInstance;
- toolbarElement = fixture.debugElement.query(By.css('mat-toolbar')).nativeElement;
- });
+ describe('with single row', () => {
+ let fixture: ComponentFixture;
+ let testComponent: ToolbarSingleRow;
+ let toolbarElement: HTMLElement;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ToolbarSingleRow);
+ testComponent = fixture.debugElement.componentInstance;
+ toolbarElement = fixture.debugElement.query(By.css('.mat-toolbar')).nativeElement;
+ });
- it('should apply class based on color attribute', () => {
- testComponent.toolbarColor = 'primary';
- fixture.detectChanges();
+ it('should apply class based on color attribute', () => {
+ testComponent.toolbarColor = 'primary';
+ fixture.detectChanges();
- expect(toolbarElement.classList.contains('mat-primary')).toBe(true);
+ expect(toolbarElement.classList.contains('mat-primary')).toBe(true);
- testComponent.toolbarColor = 'accent';
- fixture.detectChanges();
+ testComponent.toolbarColor = 'accent';
+ fixture.detectChanges();
- expect(toolbarElement.classList.contains('mat-primary')).toBe(false);
- expect(toolbarElement.classList.contains('mat-accent')).toBe(true);
+ expect(toolbarElement.classList.contains('mat-primary')).toBe(false);
+ expect(toolbarElement.classList.contains('mat-accent')).toBe(true);
- testComponent.toolbarColor = 'warn';
- fixture.detectChanges();
+ testComponent.toolbarColor = 'warn';
+ fixture.detectChanges();
- expect(toolbarElement.classList.contains('mat-accent')).toBe(false);
- expect(toolbarElement.classList.contains('mat-warn')).toBe(true);
+ expect(toolbarElement.classList.contains('mat-accent')).toBe(false);
+ expect(toolbarElement.classList.contains('mat-warn')).toBe(true);
+ });
+
+ it('should not wrap the first row contents inside of a generated element', () => {
+ expect(toolbarElement.firstElementChild!.tagName).toBe('SPAN',
+ 'Expected the element of the first row to be a direct child of the toolbar');
+ });
});
- it('should set the toolbar role on the host', () => {
- expect(toolbarElement.getAttribute('role')).toBe('toolbar');
+ describe('with multiple rows', () => {
+
+ it('should project each toolbar-row element inside of the toolbar', () => {
+ const fixture = TestBed.createComponent(ToolbarMultipleRows);
+ fixture.detectChanges();
+
+ expect(fixture.debugElement.queryAll(By.css('.mat-toolbar > .mat-toolbar-row')).length)
+ .toBe(2, 'Expected one toolbar row to be present while no content is projected.');
+ });
+
+ it('should throw an error if different toolbar modes are mixed', () => {
+ expect(() => {
+ const fixture = TestBed.createComponent(ToolbarMixedRowModes);
+ fixture.detectChanges();
+ }).toThrowError(/attempting to combine different/i);
+ });
+
+ it('should throw an error if a toolbar-row is added later', () => {
+ const fixture = TestBed.createComponent(ToolbarMixedRowModes);
+
+ fixture.componentInstance.showToolbarRow = false;
+ fixture.detectChanges();
+
+ expect(() => {
+ fixture.componentInstance.showToolbarRow = true;
+ fixture.detectChanges();
+ }).toThrowError(/attempting to combine different/i);
+ });
});
});
-@Component({template: `Test Toolbar`})
-class TestApp {
+@Component({
+ template: `
+
+ First Row
+
+ `
+})
+class ToolbarSingleRow {
toolbarColor: string;
}
+
+@Component({
+ template: `
+
+ First Row
+ Second Row
+
+ `
+})
+class ToolbarMultipleRows {}
+
+@Component({
+ template: `
+
+ First Row
+ Second Row
+
+ `
+})
+class ToolbarMixedRowModes {
+ showToolbarRow: boolean = true;
+}
diff --git a/src/lib/toolbar/toolbar.ts b/src/lib/toolbar/toolbar.ts
index 607f2c0bf02d..5bf6a1c3a539 100644
--- a/src/lib/toolbar/toolbar.ts
+++ b/src/lib/toolbar/toolbar.ts
@@ -7,22 +7,19 @@
*/
import {
+ AfterViewInit,
ChangeDetectionStrategy,
Component,
+ ContentChildren,
Directive,
ElementRef,
+ isDevMode,
+ QueryList,
Renderer2,
- ViewEncapsulation,
+ ViewEncapsulation
} from '@angular/core';
import {CanColor, mixinColor} from '@angular/material/core';
-
-
-@Directive({
- selector: 'mat-toolbar-row',
- exportAs: 'matToolbarRow',
- host: {'class': 'mat-toolbar-row'},
-})
-export class MatToolbarRow {}
+import {Platform} from '@angular/cdk/platform';
// Boilerplate for applying mixins to MatToolbar.
/** @docs-private */
@@ -31,6 +28,12 @@ export class MatToolbarBase {
}
export const _MatToolbarMixinBase = mixinColor(MatToolbarBase);
+@Directive({
+ selector: 'mat-toolbar-row',
+ exportAs: 'matToolbarRow',
+ host: {'class': 'mat-toolbar-row'},
+})
+export class MatToolbarRow {}
@Component({
moduleId: module.id,
@@ -41,16 +44,58 @@ export const _MatToolbarMixinBase = mixinColor(MatToolbarBase);
inputs: ['color'],
host: {
'class': 'mat-toolbar',
- 'role': 'toolbar'
+ '[class.mat-toolbar-multiple-rows]': 'this._toolbarRows.length',
+ '[class.mat-toolbar-single-row]': '!this._toolbarRows.length'
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
})
-export class MatToolbar extends _MatToolbarMixinBase implements CanColor {
+export class MatToolbar extends _MatToolbarMixinBase implements CanColor, AfterViewInit {
+
+ /** Reference to all toolbar row elements that have been projected. */
+ @ContentChildren(MatToolbarRow) _toolbarRows: QueryList;
- constructor(renderer: Renderer2, elementRef: ElementRef) {
+ constructor(renderer: Renderer2, elementRef: ElementRef, private _platform: Platform) {
super(renderer, elementRef);
}
+ ngAfterViewInit() {
+ if (!isDevMode() || !this._platform.isBrowser) {
+ return;
+ }
+
+ this._checkToolbarMixedModes();
+ this._toolbarRows.changes.subscribe(() => this._checkToolbarMixedModes());
+ }
+
+ /**
+ * Throws an exception when developers are attempting to combine the different toolbar row modes.
+ */
+ private _checkToolbarMixedModes() {
+ if (!this._toolbarRows.length) {
+ return;
+ }
+
+ // Check if there are any other DOM nodes that can display content but aren't inside of
+ // a element.
+ const isCombinedUsage = [].slice.call(this._elementRef.nativeElement.childNodes)
+ .filter(node => !(node.classList && node.classList.contains('mat-toolbar-row')))
+ .filter(node => node.nodeType !== Node.COMMENT_NODE)
+ .some(node => node.textContent.trim());
+
+ if (isCombinedUsage) {
+ throwToolbarMixedModesError();
+ }
+ }
+}
+
+/**
+ * Throws an exception when attempting to combine the different toolbar row modes.
+ * @docs-private
+ */
+export function throwToolbarMixedModesError() {
+ throw Error('MatToolbar: Attempting to combine different toolbar modes. ' +
+ 'Either specify multiple `` elements explicitly or just place content ' +
+ 'inside of a `` for a single row.');
}
diff --git a/src/material-examples/toolbar-multirow/toolbar-multirow-example.html b/src/material-examples/toolbar-multirow/toolbar-multirow-example.html
index a0c0e9cfde84..c1731969a509 100644
--- a/src/material-examples/toolbar-multirow/toolbar-multirow-example.html
+++ b/src/material-examples/toolbar-multirow/toolbar-multirow-example.html
@@ -1,5 +1,7 @@
- Custom Toolbar
+
+ Custom Toolbar
+
Second Line