diff --git a/src/demo-app/demo-app-module.ts b/src/demo-app/demo-app-module.ts
index 2cce8f9be446..38ec54b7ce1f 100644
--- a/src/demo-app/demo-app-module.ts
+++ b/src/demo-app/demo-app-module.ts
@@ -41,6 +41,7 @@ import {DataTableDemo} from './data-table/data-table-demo';
import {PeopleDatabase} from './data-table/people-database';
import {DatepickerDemo} from './datepicker/datepicker-demo';
import {TypographyDemo} from './typography/typography-demo';
+import {ExpansionDemo} from './expansion/expansion-demo';
import {
CdkDataTableModule,
FullscreenOverlayContainer,
@@ -53,6 +54,7 @@ import {
MdCoreModule,
MdDatepickerModule,
MdDialogModule,
+ MdExpansionModule,
MdGridListModule,
MdIconModule,
MdInputModule,
@@ -87,6 +89,7 @@ import {
MdChipsModule,
MdDatepickerModule,
MdDialogModule,
+ MdExpansionModule,
MdGridListModule,
MdIconModule,
MdInputModule,
@@ -170,6 +173,7 @@ export class DemoMaterialModule {}
FoggyTabContent,
PlatformDemo,
TypographyDemo,
+ ExpansionDemo,
],
providers: [
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
diff --git a/src/demo-app/demo-app/demo-app.ts b/src/demo-app/demo-app/demo-app.ts
index 57be232775fe..ff358a37abb3 100644
--- a/src/demo-app/demo-app/demo-app.ts
+++ b/src/demo-app/demo-app/demo-app.ts
@@ -43,6 +43,7 @@ export class DemoApp {
{name: 'Data Table', route: 'data-table'},
{name: 'Datepicker', route: 'datepicker'},
{name: 'Dialog', route: 'dialog'},
+ {name: 'Expansion Panel', route: 'expansion'},
{name: 'Gestures', route: 'gestures'},
{name: 'Grid List', route: 'grid-list'},
{name: 'Icon', route: 'icon'},
diff --git a/src/demo-app/demo-app/routes.ts b/src/demo-app/demo-app/routes.ts
index a23ab93b5561..beca55e41423 100644
--- a/src/demo-app/demo-app/routes.ts
+++ b/src/demo-app/demo-app/routes.ts
@@ -35,6 +35,7 @@ import {StyleDemo} from '../style/style-demo';
import {DatepickerDemo} from '../datepicker/datepicker-demo';
import {DataTableDemo} from '../data-table/data-table-demo';
import {TypographyDemo} from '../typography/typography-demo';
+import {ExpansionDemo} from '../expansion/expansion-demo';
export const DEMO_APP_ROUTES: Routes = [
{path: '', component: Home},
@@ -71,5 +72,6 @@ export const DEMO_APP_ROUTES: Routes = [
{path: 'snack-bar', component: SnackBarDemo},
{path: 'platform', component: PlatformDemo},
{path: 'style', component: StyleDemo},
- {path: 'typography', component: TypographyDemo}
+ {path: 'typography', component: TypographyDemo},
+ {path: 'expansion', component: ExpansionDemo},
];
diff --git a/src/demo-app/expansion/expansion-demo.html b/src/demo-app/expansion/expansion-demo.html
new file mode 100644
index 000000000000..fe2fa05517de
--- /dev/null
+++ b/src/demo-app/expansion/expansion-demo.html
@@ -0,0 +1,52 @@
+
Single Expansion Panel
+
+
+ This is a panel description.
+ Panel Title
+
+ This is the content text that makes sense here.
+
+
+
+
+
+
+Accordion
+
+
Accordion Options
+
+ Allow Multi Expansion
+ Hide Indicators
+ Show Panel 3
+
+
Accordion Style
+
+ Default
+ Flat
+
+
Accordion Panel(s)
+
+ Panel 1
+ Panel 2
+
+
+
+
+
+ Section 1
+ This is the content text that makes sense here.
+
+
+ Section 2
+ This is the content text that makes sense here.
+
+
+ Section 3
+ Reveal Buttons Below
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/demo-app/expansion/expansion-demo.scss b/src/demo-app/expansion/expansion-demo.scss
new file mode 100644
index 000000000000..20e4bbd80427
--- /dev/null
+++ b/src/demo-app/expansion/expansion-demo.scss
@@ -0,0 +1,4 @@
+.md-expansion-demo-width {
+ width: 600px;
+ display: block;
+}
\ No newline at end of file
diff --git a/src/demo-app/expansion/expansion-demo.ts b/src/demo-app/expansion/expansion-demo.ts
new file mode 100644
index 000000000000..170ebc3c441c
--- /dev/null
+++ b/src/demo-app/expansion/expansion-demo.ts
@@ -0,0 +1,15 @@
+import {Component, ViewEncapsulation} from '@angular/core';
+
+@Component({
+ moduleId: module.id,
+ selector: 'expansion-demo',
+ styleUrls: ['expansion-demo.css'],
+ templateUrl: 'expansion-demo.html',
+ encapsulation: ViewEncapsulation.None,
+})
+export class ExpansionDemo {
+ displayMode: string = 'default';
+ multi: boolean = false;
+ hideToggle: boolean = false;
+ showPanel3 = true;
+}
diff --git a/src/lib/core/theming/_all-theme.scss b/src/lib/core/theming/_all-theme.scss
index 374225788931..bfd0523981b2 100644
--- a/src/lib/core/theming/_all-theme.scss
+++ b/src/lib/core/theming/_all-theme.scss
@@ -8,6 +8,7 @@
@import '../../chips/chips-theme';
@import '../../datepicker/datepicker-theme';
@import '../../dialog/dialog-theme';
+@import '../../expansion/expansion-theme';
@import '../../grid-list/grid-list-theme';
@import '../../icon/icon-theme';
@import '../../input/input-theme';
@@ -37,6 +38,7 @@
@include mat-chips-theme($theme);
@include mat-datepicker-theme($theme);
@include mat-dialog-theme($theme);
+ @include mat-expansion-panel-theme($theme);
@include mat-grid-list-theme($theme);
@include mat-icon-theme($theme);
@include mat-input-theme($theme);
diff --git a/src/lib/expansion/_expansion-theme.scss b/src/lib/expansion/_expansion-theme.scss
new file mode 100644
index 000000000000..2ed9552afdc4
--- /dev/null
+++ b/src/lib/expansion/_expansion-theme.scss
@@ -0,0 +1,32 @@
+@import '../core/theming/palette';
+@import '../core/theming/theming';
+
+@mixin mat-expansion-panel-theme($theme) {
+ $background: map-get($theme, background);
+ $foreground: map-get($theme, foreground);
+
+ .mat-expansion-panel {
+ background: mat-color($background, card);
+ color: mat-color($foreground, base);
+ }
+
+ .mat-action-row {
+ border-top-color: mat-color($foreground, divider);
+ }
+
+ .mat-expansion-panel-header:focus,
+ .mat-expansion-panel-header:hover {
+ background: mat-color($background, hover);
+ }
+ .mat-expansion-panel-header-title {
+ color: mat-color($foreground, text);
+ }
+
+ .mat-expansion-panel-header-description {
+ color: mat-color($foreground, secondary-text);
+ }
+
+ .mat-expansion-indicator::after {
+ color: mat-color($foreground, secondary-text);
+ }
+}
\ No newline at end of file
diff --git a/src/lib/expansion/accordion-item.ts b/src/lib/expansion/accordion-item.ts
new file mode 100644
index 000000000000..95c57ee74126
--- /dev/null
+++ b/src/lib/expansion/accordion-item.ts
@@ -0,0 +1,72 @@
+import {Output, EventEmitter, Input, Injectable, OnDestroy, Optional} from '@angular/core';
+import {UniqueSelectionDispatcher} from '../core';
+import {CdkAccordion} from './accordion';
+
+/** Used to generate unique ID for each expansion panel. */
+let nextId = 0;
+
+/**
+ * An abstract class to be extended and decorated as a component. Sets up all
+ * events and attributes needed to be managed by a CdkAccordion parent.
+ */
+@Injectable()
+export class AccordionItem implements OnDestroy {
+ /** Event emitted every time the MdAccordianChild is closed. */
+ @Output() closed = new EventEmitter();
+ /** Event emitted every time the MdAccordianChild is opened. */
+ @Output() opened = new EventEmitter();
+ /** Event emitted when the MdAccordianChild is destroyed. */
+ @Output() destroyed = new EventEmitter();
+ /** The unique MdAccordianChild id. */
+ readonly id = `cdk-accordion-child-${nextId++}`;
+ /** Whether the MdAccordianChild is expanded. */
+ @Input() get expanded(): boolean { return this._expanded; }
+ set expanded(expanded: boolean) {
+ // Only emit events and update the internal value if the value changes.
+ if (this._expanded !== expanded) {
+ this._expanded = expanded;
+ if (expanded) {
+ this.opened.emit();
+ /**
+ * In the unique selection dispatcher, the id parameter is the id of the CdkAccordonItem,
+ * the name value is the id of the accordion.
+ */
+ let accordionId = this.accordion ? this.accordion.id : this.id;
+ this._expansionDispatcher.notify(this.id, accordionId);
+ } else {
+ this.closed.emit();
+ }
+ }
+ }
+ private _expanded: boolean;
+
+ constructor(@Optional() public accordion: CdkAccordion,
+ protected _expansionDispatcher: UniqueSelectionDispatcher) {
+ _expansionDispatcher.listen((id: string, accordionId: string) => {
+ if (this.accordion && !this.accordion.multi &&
+ this.accordion.id === accordionId && this.id !== id) {
+ this.expanded = false;
+ }
+ });
+ }
+
+ /** Emits an event for the accordion item being destroyed. */
+ ngOnDestroy() {
+ this.destroyed.emit();
+ }
+
+ /** Toggles the expanded state of the accordion item. */
+ toggle(): void {
+ this.expanded = !this.expanded;
+ }
+
+ /** Sets the expanded state of the accordion item to false. */
+ close(): void {
+ this.expanded = false;
+ }
+
+ /** Sets the expanded state of the accordion item to true. */
+ open(): void {
+ this.expanded = true;
+ }
+}
diff --git a/src/lib/expansion/accordion.spec.ts b/src/lib/expansion/accordion.spec.ts
new file mode 100644
index 000000000000..09e198eac768
--- /dev/null
+++ b/src/lib/expansion/accordion.spec.ts
@@ -0,0 +1,66 @@
+import {async, TestBed} from '@angular/core/testing';
+import {Component} from '@angular/core';
+import {By} from '@angular/platform-browser';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {MdExpansionModule} from './index';
+
+
+describe('CdkAccordion', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ BrowserAnimationsModule,
+ MdExpansionModule
+ ],
+ declarations: [
+ SetOfItems
+ ],
+ });
+ TestBed.compileComponents();
+ }));
+
+ it('should ensure only one item is expanded at a time', () => {
+ let fixture = TestBed.createComponent(SetOfItems);
+ let items = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'));
+
+ fixture.componentInstance.firstPanelExpanded = true;
+ fixture.detectChanges();
+ expect(items[0].classes['mat-expanded']).toBeTruthy();
+ expect(items[1].classes['mat-expanded']).toBeFalsy();
+
+ fixture.componentInstance.secondPanelExpanded = true;
+ fixture.detectChanges();
+ expect(items[0].classes['mat-expanded']).toBeFalsy();
+ expect(items[1].classes['mat-expanded']).toBeTruthy();
+ });
+
+ it('should allow multiple items to be expanded simultaneously', () => {
+ let fixture = TestBed.createComponent(SetOfItems);
+ let panels = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'));
+
+ fixture.componentInstance.multi = true;
+ fixture.componentInstance.firstPanelExpanded = true;
+ fixture.componentInstance.secondPanelExpanded = true;
+ fixture.detectChanges();
+ expect(panels[0].classes['mat-expanded']).toBeTruthy();
+ expect(panels[1].classes['mat-expanded']).toBeTruthy();
+ });
+});
+
+
+@Component({template: `
+
+
+ Summary
+ Content
+
+
+ Summary
+ Content
+
+ `})
+class SetOfItems {
+ multi: boolean = false;
+ firstPanelExpanded: boolean = false;
+ secondPanelExpanded: boolean = false;
+}
diff --git a/src/lib/expansion/accordion.ts b/src/lib/expansion/accordion.ts
new file mode 100644
index 000000000000..804887d3772f
--- /dev/null
+++ b/src/lib/expansion/accordion.ts
@@ -0,0 +1,50 @@
+import {Directive, Input} from '@angular/core';
+import {coerceBooleanProperty} from '../core/coercion/boolean-property';
+
+/** MdAccordion's display modes. */
+export type MdAccordionDisplayMode = 'default' | 'flat';
+
+/** Unique ID counter */
+let nextId = 0;
+
+/**
+ * Directive whose purpose is to manage the expanded state of CdkAccordionItem children.
+ */
+@Directive({
+ selector: '[cdk-accordion]',
+})
+export class CdkAccordion {
+ /** A readonly id value to use for unique selection coordination. */
+ readonly id = `cdk-accordion-${nextId++}`;
+
+ /** Whether the accordion should allow multiple expanded accordion items simulateously. */
+ @Input() get multi(): boolean { return this._multi; }
+ set multi(multi: boolean) { this._multi = coerceBooleanProperty(multi); }
+ private _multi: boolean = false;
+
+ /** Whether the expansion indicator should be hidden. */
+ @Input() get hideToggle(): boolean { return this._hideToggle; }
+ set hideToggle(show: boolean) { this._hideToggle = coerceBooleanProperty(show); }
+ private _hideToggle: boolean = false;
+
+ /**
+ * The display mode used for all expansion panels in the accordion. Currently two display
+ * modes exist:
+ * default - a gutter-like spacing is placed around any expanded panel, placing the expanded
+ * panel at a different elevation from the reset of the accordion.
+ * flat - no spacing is placed around expanded panels, showing all panels at the same
+ * elevation.
+ */
+ @Input() displayMode: MdAccordionDisplayMode = 'default';
+}
+
+/**
+ * Directive for a Material Design Accordion.
+ */
+@Directive({
+ selector: 'mat-accordion, md-accordion',
+ host: {
+ class: 'mat-accordion'
+ }
+})
+export class MdAccordion extends CdkAccordion {}
diff --git a/src/lib/expansion/expansion-panel-header.html b/src/lib/expansion/expansion-panel-header.html
new file mode 100644
index 000000000000..564a957bf803
--- /dev/null
+++ b/src/lib/expansion/expansion-panel-header.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/expansion/expansion-panel-header.scss b/src/lib/expansion/expansion-panel-header.scss
new file mode 100644
index 000000000000..d88885d34396
--- /dev/null
+++ b/src/lib/expansion/expansion-panel-header.scss
@@ -0,0 +1,61 @@
+$mat-expansion-panel-header-height: 48px;
+$mat-expansion-panel-header-height-expanded: 64px;
+
+.mat-expansion-panel-header {
+ cursor: pointer;
+ display: flex;
+ flex-direction: row;
+ height: $mat-expansion-panel-header-height;
+ line-height: $mat-expansion-panel-header-height;
+ padding: 0 24px;
+
+ &.mat-expanded {
+ height: $mat-expansion-panel-header-height-expanded;
+ line-height: $mat-expansion-panel-header-height-expanded;
+ }
+
+ &:focus,
+ &:hover {
+ outline: none;
+ }
+
+ &.mat-expanded:focus,
+ &.mat-expanded:hover, {
+ background: inherit;
+ }
+}
+
+.mat-content {
+ display: flex;
+ flex: 1;
+ flex-direction: row;
+ overflow: hidden;
+}
+
+.mat-expansion-panel-header-title {
+ display: flex;
+ flex-grow: 1;
+ font-size: 15px;
+ margin-right: 16px;
+}
+
+.mat-expansion-panel-header-description {
+ display: flex;
+ flex-grow: 2;
+ font-size: 15px;
+ margin-right: 16px;
+}
+
+/**
+ * Creates the expansion indicator arrow. Done using ::after rather than having
+ * additional nodes in the template.
+ */
+.mat-expansion-indicator::after {
+ border-style: solid;
+ border-width: 0 2px 2px 0;
+ content: '';
+ display: inline-block;
+ padding: 3px;
+ transform: rotate(45deg);
+ vertical-align: middle;
+}
diff --git a/src/lib/expansion/expansion-panel-header.ts b/src/lib/expansion/expansion-panel-header.ts
new file mode 100644
index 000000000000..f3f631cb1038
--- /dev/null
+++ b/src/lib/expansion/expansion-panel-header.ts
@@ -0,0 +1,122 @@
+import {
+ Component,
+ Directive,
+ Host,
+ ViewEncapsulation,
+} from '@angular/core';
+import {
+ trigger,
+ state,
+ style,
+ transition,
+ animate,
+} from '@angular/animations';
+import {SPACE, ENTER} from '../core/keyboard/keycodes';
+import {MdExpansionPanel, EXPANSION_PANEL_ANIMATION_TIMING} from './expansion-panel';
+
+
+/**
+ * component.
+ *
+ * This component corresponds to the header element of an .
+ *
+ * Please refer to README.md for examples on how to use it.
+ */
+@Component({
+ moduleId: module.id,
+ selector: 'md-expansion-panel-header, mat-expansion-panel-header',
+ styleUrls: ['./expansion-panel-header.css'],
+ templateUrl: './expansion-panel-header.html',
+ encapsulation: ViewEncapsulation.None,
+ host: {
+ 'class': 'mat-expansion-panel-header',
+ 'role': 'button',
+ 'tabindex': '0',
+ '[attr.aria-controls]': '_getPanelId()',
+ '[attr.aria-expanded]': '_isExpanded()',
+ '[class.mat-expanded]': '_isExpanded()',
+ '(click)': '_toggle()',
+ '(keyup)': '_keyup($event)',
+ '[@expansionHeight]': '_getExpandedState()',
+ },
+ animations: [
+ trigger('indicatorRotate', [
+ state('collapsed', style({transform: 'rotate(0deg)'})),
+ state('expanded', style({transform: 'rotate(180deg)'})),
+ transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)),
+ ]),
+ trigger('expansionHeight', [
+ state('collapsed', style({height: '48px', 'line-height': '48px'})),
+ state('expanded', style({height: '64px', 'line-height': '68px'})),
+ transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)),
+ ]),
+ ],
+})
+export class MdExpansionPanelHeader {
+ constructor(@Host() public panel: MdExpansionPanel) {}
+
+ /** Toggles the expanded state of the panel. */
+ _toggle(event?: KeyboardEvent): void {
+ this.panel.toggle();
+ }
+
+ /** Gets whether the panel is expanded. */
+ _isExpanded(): boolean {
+ return this.panel.expanded;
+ }
+
+ /** Gets the expanded state string of the panel. */
+ _getExpandedState(): string {
+ return this.panel._getExpandedState();
+ }
+
+ /** Gets the panel id. */
+ _getPanelId(): string {
+ return this.panel.id;
+ }
+
+ /** Gets whether the expand indicator is hidden. */
+ _getHideToggle(): boolean {
+ return this.panel.hideToggle;
+ }
+
+ /** Handle keyup event calling to toggle() if appropriate. */
+ _keyup(event: KeyboardEvent) {
+ switch (event.keyCode) {
+ // Toggle for space and enter keys.
+ case SPACE:
+ case ENTER:
+ event.preventDefault();
+ this._toggle();
+ break;
+ default:
+ return;
+ }
+ }
+}
+
+/**
+ * directive.
+ *
+ * This direction is to be used inside of the MdExpansionPanelHeader component.
+ */
+@Directive({
+ selector: 'md-panel-description, mat-panel-description',
+ host : {
+ class: 'mat-expansion-panel-header-description'
+ }
+})
+export class MdExpansionPanelDescription {}
+
+/**
+ * directive.
+ *
+ * This direction is to be used inside of the MdExpansionPanelHeader component.
+ */
+@Directive({
+ selector: 'md-panel-title, mat-panel-title',
+ host : {
+ class: 'mat-expansion-panel-header-title'
+ }
+})
+export class MdExpansionPanelTitle {}
diff --git a/src/lib/expansion/expansion-panel.html b/src/lib/expansion/expansion-panel.html
new file mode 100644
index 000000000000..25422ebc6f70
--- /dev/null
+++ b/src/lib/expansion/expansion-panel.html
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/src/lib/expansion/expansion-panel.scss b/src/lib/expansion/expansion-panel.scss
new file mode 100644
index 000000000000..0a258ad43037
--- /dev/null
+++ b/src/lib/expansion/expansion-panel.scss
@@ -0,0 +1,33 @@
+@import '../core/style/variables';
+@import '../core/style/elevation';
+
+.mat-expansion-panel {
+ @include mat-elevation-transition;
+ box-sizing: content-box;
+ display: block;
+
+ &:not([class*='mat-elevation-z']) {
+ @include mat-elevation(2);
+ }
+}
+
+.mat-expansion-panel-content {
+ overflow: hidden;
+}
+
+.mat-expansion-panel-body {
+ padding: 0 24px 16px;
+}
+
+.mat-action-row {
+ border-top-style: solid;
+ border-top-width: 1px;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ padding: 16px 8px 16px 24px;
+
+ button.mat-button {
+ margin-left: 8px;
+ }
+}
\ No newline at end of file
diff --git a/src/lib/expansion/expansion-panel.ts b/src/lib/expansion/expansion-panel.ts
new file mode 100644
index 000000000000..7c7c960c8a48
--- /dev/null
+++ b/src/lib/expansion/expansion-panel.ts
@@ -0,0 +1,106 @@
+import {
+ Component,
+ Directive,
+ Host,
+ Input,
+ ViewEncapsulation,
+ Optional,
+ forwardRef,
+} from '@angular/core';
+import {
+ trigger,
+ state,
+ style,
+ transition,
+ animate,
+} from '@angular/animations';
+import {MdAccordion, MdAccordionDisplayMode} from './accordion';
+import {AccordionItem} from './accordion-item';
+import {UniqueSelectionDispatcher} from '../core';
+
+
+/** MdExpansionPanel's states. */
+export type MdExpansionPanelState = 'expanded' | 'collapsed';
+
+/** Time and timing curve for expansion panel animations. */
+export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2,1)';
+
+/**
+ * component.
+ *
+ * This component can be used as a single element to show expandable content, or as one of
+ * multiple children of an element with the CdkAccordion directive attached.
+ *
+ * Please refer to README.md for examples on how to use it.
+ */
+@Component({
+ moduleId: module.id,
+ styleUrls: ['./expansion-panel.css'],
+ selector: 'md-expansion-panel, mat-expansion-panel',
+ templateUrl: './expansion-panel.html',
+ encapsulation: ViewEncapsulation.None,
+ host: {
+ 'class': 'mat-expansion-panel',
+ '[class.mat-expanded]': 'expanded',
+ '[@displayMode]': '_getDisplayMode()',
+ },
+ providers: [
+ {provide: AccordionItem, useExisting: forwardRef(() => MdExpansionPanel)}
+ ],
+ animations: [
+ trigger('bodyExpansion', [
+ state('collapsed', style({height: '0px'})),
+ state('expanded', style({height: '*'})),
+ transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)),
+ ]),
+ trigger('displayMode', [
+ state('collapsed', style({margin: '0'})),
+ state('default', style({margin: '16px 0'})),
+ state('flat', style({margin: '0'})),
+ transition('flat <=> collapsed, default <=> collapsed, flat <=> default',
+ animate(EXPANSION_PANEL_ANIMATION_TIMING)),
+ ]),
+ ],
+})
+export class MdExpansionPanel extends AccordionItem {
+ /** Whether the toggle indicator should be hidden. */
+ @Input() hideToggle: boolean = false;
+
+ constructor(@Optional() @Host() accordion: MdAccordion,
+ _uniqueSelectionDispatcher: UniqueSelectionDispatcher) {
+ super(accordion, _uniqueSelectionDispatcher);
+ this.accordion = accordion;
+ }
+
+ /** Whether the expansion indicator should be hidden. */
+ _getHideToggle(): boolean {
+ if (this.accordion) {
+ return this.accordion.hideToggle;
+ }
+ return this.hideToggle;
+ }
+
+ /** Gets the panel's display mode. */
+ _getDisplayMode(): MdAccordionDisplayMode | MdExpansionPanelState {
+ if (!this.expanded) {
+ return this._getExpandedState();
+ }
+ if (this.accordion) {
+ return this.accordion.displayMode;
+ }
+ return this._getExpandedState();
+ }
+
+ /** Gets the expanded state string. */
+ _getExpandedState(): MdExpansionPanelState {
+ return this.expanded ? 'expanded' : 'collapsed';
+ }
+}
+
+@Directive({
+ selector: 'mat-action-row, md-action-row',
+ host: {
+ class: 'mat-action-row'
+ }
+})
+export class MdExpansionPanelActionRow {}
diff --git a/src/lib/expansion/expansion.spec.ts b/src/lib/expansion/expansion.spec.ts
new file mode 100644
index 000000000000..e6f53a7d6437
--- /dev/null
+++ b/src/lib/expansion/expansion.spec.ts
@@ -0,0 +1,73 @@
+import {async, TestBed} from '@angular/core/testing';
+import {Component} from '@angular/core';
+import {By} from '@angular/platform-browser';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {MdExpansionModule} from './index';
+
+
+describe('MdExpansionPanel', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ BrowserAnimationsModule,
+ MdExpansionModule
+ ],
+ declarations: [
+ PanelWithContent
+ ],
+ });
+ TestBed.compileComponents();
+ }));
+
+ it('should expanded and collapse the panel', () => {
+ let fixture = TestBed.createComponent(PanelWithContent);
+ let contentEl = fixture.debugElement.query(By.css('.mat-expansion-panel-content'));
+ let headerEl = fixture.debugElement.query(By.css('.mat-expansion-panel-header'));
+ fixture.detectChanges();
+ expect(headerEl.classes['mat-expanded']).toBeFalsy();
+ expect(contentEl.classes['mat-expanded']).toBeFalsy();
+
+ fixture.componentInstance.expanded = true;
+ fixture.detectChanges();
+ expect(headerEl.classes['mat-expanded']).toBeTruthy();
+ expect(contentEl.classes['mat-expanded']).toBeTruthy();
+ });
+
+ it('emit correct events for change in panel expanded state', () => {
+ let fixture = TestBed.createComponent(PanelWithContent);
+ fixture.componentInstance.expanded = true;
+ fixture.detectChanges();
+ expect(fixture.componentInstance.openCallback).toHaveBeenCalled();
+
+ fixture.componentInstance.expanded = false;
+ fixture.detectChanges();
+ expect(fixture.componentInstance.closeCallback).toHaveBeenCalled();
+ });
+
+ it('creates a unique panel id for each panel', () => {
+ let fixtureOne = TestBed.createComponent(PanelWithContent);
+ let headerElOne = fixtureOne.nativeElement.querySelector('.mat-expansion-panel-header');
+ let fixtureTwo = TestBed.createComponent(PanelWithContent);
+ let headerElTwo = fixtureTwo.nativeElement.querySelector('.mat-expansion-panel-header');
+ fixtureOne.detectChanges();
+ fixtureTwo.detectChanges();
+
+ let panelIdOne = headerElOne.getAttribute('aria-controls');
+ let panelIdTwo = headerElTwo.getAttribute('aria-controls');
+ expect(panelIdOne).not.toBe(panelIdTwo);
+ });
+});
+
+
+@Component({template: `
+
+ Panel Title
+ Some content
+ `})
+class PanelWithContent {
+ expanded: boolean = false;
+ openCallback = jasmine.createSpy('openCallback');
+ closeCallback = jasmine.createSpy('closeCallback');
+}
diff --git a/src/lib/expansion/index.ts b/src/lib/expansion/index.ts
new file mode 100644
index 000000000000..9dbeb268a2e1
--- /dev/null
+++ b/src/lib/expansion/index.ts
@@ -0,0 +1,57 @@
+import {NgModule, ModuleWithProviders} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {CompatibilityModule, UNIQUE_SELECTION_DISPATCHER_PROVIDER} from '../core';
+import {
+ MdExpansionPanelHeader,
+ MdExpansionPanelDescription,
+ MdExpansionPanelTitle
+} from './expansion-panel-header';
+import {
+ MdExpansionPanel,
+ MdExpansionPanelActionRow,
+} from './expansion-panel';
+import {
+ CdkAccordion,
+ MdAccordion,
+} from './accordion';
+
+@NgModule({
+ imports: [CompatibilityModule, CommonModule],
+ exports: [
+ CdkAccordion,
+ MdAccordion,
+ MdExpansionPanel,
+ MdExpansionPanelActionRow,
+ MdExpansionPanelHeader,
+ MdExpansionPanelTitle,
+ MdExpansionPanelDescription
+ ],
+ declarations: [
+ CdkAccordion,
+ MdAccordion,
+ MdExpansionPanel,
+ MdExpansionPanelActionRow,
+ MdExpansionPanelHeader,
+ MdExpansionPanelTitle,
+ MdExpansionPanelDescription
+ ],
+ providers: [UNIQUE_SELECTION_DISPATCHER_PROVIDER]
+})
+export class MdExpansionModule {}
+
+export {
+ CdkAccordion,
+ MdAccordion,
+ MdAccordionDisplayMode
+} from './accordion';
+export {AccordionItem} from './accordion-item';
+export {
+ MdExpansionPanel,
+ MdExpansionPanelState,
+ MdExpansionPanelActionRow
+} from './expansion-panel';
+export {
+ MdExpansionPanelHeader,
+ MdExpansionPanelDescription,
+ MdExpansionPanelTitle
+} from './expansion-panel-header';
diff --git a/src/lib/module.ts b/src/lib/module.ts
index 255ba9b5d200..2041b0d2c7e5 100644
--- a/src/lib/module.ts
+++ b/src/lib/module.ts
@@ -37,6 +37,7 @@ import {MdAutocompleteModule} from './autocomplete/index';
import {StyleModule} from './core/style/index';
import {MdDatepickerModule} from './datepicker/index';
import {CdkDataTableModule} from './core/data-table/index';
+import {MdExpansionModule} from './expansion/index';
const MATERIAL_MODULES = [
MdAutocompleteModule,
@@ -47,6 +48,7 @@ const MATERIAL_MODULES = [
MdCheckboxModule,
MdDatepickerModule,
MdDialogModule,
+ MdExpansionModule,
MdGridListModule,
MdIconModule,
MdInputModule,
diff --git a/src/lib/public_api.ts b/src/lib/public_api.ts
index 7eeeb82012d1..1a7a77478014 100644
--- a/src/lib/public_api.ts
+++ b/src/lib/public_api.ts
@@ -16,6 +16,7 @@ export * from './checkbox/index';
export * from './core/data-table/index';
export * from './datepicker/index';
export * from './dialog/index';
+export * from './expansion/index';
export * from './grid-list/index';
export * from './icon/index';
export * from './input/index';
diff --git a/tools/gulp/packaging/rollup-helpers.ts b/tools/gulp/packaging/rollup-helpers.ts
index cbed6e3fc58f..15e37c06d9da 100644
--- a/tools/gulp/packaging/rollup-helpers.ts
+++ b/tools/gulp/packaging/rollup-helpers.ts
@@ -24,6 +24,10 @@ const ROLLUP_GLOBALS = {
'@angular/cdk': 'ng.cdk',
// Rxjs dependencies
+ 'rxjs/BehaviorSubject': 'Rx',
+ 'rxjs/Observable': 'Rx',
+ 'rxjs/Subject': 'Rx',
+ 'rxjs/Subscription': 'Rx',
'rxjs/add/observable/combineLatest': 'Rx.Observable',
'rxjs/add/observable/forkJoin': 'Rx.Observable',
'rxjs/add/observable/fromEvent': 'Rx.Observable',
@@ -44,11 +48,6 @@ const ROLLUP_GLOBALS = {
'rxjs/add/operator/switchMap': 'Rx.Observable.prototype',
'rxjs/add/operator/takeUntil': 'Rx.Observable.prototype',
'rxjs/add/operator/toPromise': 'Rx.Observable.prototype',
- 'rxjs/BehaviorSubject': 'Rx',
- 'rxjs/Observable': 'Rx',
- 'rxjs/Subject': 'Rx',
- 'rxjs/Subscription': 'Rx',
-
};
export type BundleConfig = {