diff --git a/src/demo-app/expansion/expansion-demo.html b/src/demo-app/expansion/expansion-demo.html index fe2fa05517de..3b5976712c58 100644 --- a/src/demo-app/expansion/expansion-demo.html +++ b/src/demo-app/expansion/expansion-demo.html @@ -17,6 +17,7 @@

Accordion

Allow Multi Expansion Hide Indicators + Disable Panel 2 Show Panel 3

Accordion Style

@@ -37,7 +38,7 @@

Accordion

Section 1

This is the content text that makes sense here.

- + Section 2

This is the content text that makes sense here.

@@ -49,4 +50,4 @@

Accordion

- \ No newline at end of file + diff --git a/src/demo-app/expansion/expansion-demo.ts b/src/demo-app/expansion/expansion-demo.ts index 170ebc3c441c..ac1076b020c0 100644 --- a/src/demo-app/expansion/expansion-demo.ts +++ b/src/demo-app/expansion/expansion-demo.ts @@ -9,7 +9,8 @@ import {Component, ViewEncapsulation} from '@angular/core'; }) export class ExpansionDemo { displayMode: string = 'default'; - multi: boolean = false; - hideToggle: boolean = false; + multi = false; + hideToggle = false; + disabled = false; showPanel3 = true; } diff --git a/src/lib/expansion/_expansion-theme.scss b/src/lib/expansion/_expansion-theme.scss index 11dde9e67994..75e2b528bc3b 100644 --- a/src/lib/expansion/_expansion-theme.scss +++ b/src/lib/expansion/_expansion-theme.scss @@ -15,7 +15,7 @@ border-top-color: mat-color($foreground, divider); } - .mat-expansion-panel-header { + .mat-expansion-panel-header:not([aria-disabled='true']) { &.cdk-keyboard-focused, &.cdk-program-focused, &:hover { @@ -31,6 +31,15 @@ .mat-expansion-indicator::after { color: mat-color($foreground, secondary-text); } + + .mat-expansion-panel-header[aria-disabled='true'] { + color: mat-color($foreground, disabled-button); + + .mat-expansion-panel-header-title, + .mat-expansion-panel-header-description { + color: inherit; + } + } } @mixin mat-expansion-panel-typography($config) { diff --git a/src/lib/expansion/accordion-item.ts b/src/lib/expansion/accordion-item.ts index 785a79755f71..bf4d6d042722 100644 --- a/src/lib/expansion/accordion-item.ts +++ b/src/lib/expansion/accordion-item.ts @@ -17,16 +17,22 @@ import { } from '@angular/core'; import {UniqueSelectionDispatcher} from '../core'; import {CdkAccordion} from './accordion'; +import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled'; /** Used to generate unique ID for each expansion panel. */ let nextId = 0; +// Boilerplate for applying mixins to MdSlider. +/** @docs-private */ +export class AccordionItemBase { } +export const _AccordionItemMixinBase = mixinDisabled(AccordionItemBase); + /** * 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 { +export class AccordionItem extends _AccordionItemMixinBase implements OnDestroy, CanDisable { /** Event emitted every time the MdAccordionChild is closed. */ @Output() closed = new EventEmitter(); /** Event emitted every time the MdAccordionChild is opened. */ @@ -68,6 +74,9 @@ export class AccordionItem implements OnDestroy { constructor(@Optional() public accordion: CdkAccordion, private _changeDetectorRef: ChangeDetectorRef, protected _expansionDispatcher: UniqueSelectionDispatcher) { + + super(); + this._removeUniqueSelectionListener = _expansionDispatcher.listen((id: string, accordionId: string) => { if (this.accordion && !this.accordion.multi && diff --git a/src/lib/expansion/expansion-panel-header.html b/src/lib/expansion/expansion-panel-header.html index 4394f4b5742d..20373a9cb3ef 100644 --- a/src/lib/expansion/expansion-panel-header.html +++ b/src/lib/expansion/expansion-panel-header.html @@ -3,5 +3,5 @@ - diff --git a/src/lib/expansion/expansion-panel-header.scss b/src/lib/expansion/expansion-panel-header.scss index d501ceb2e3df..73a6638a44be 100644 --- a/src/lib/expansion/expansion-panel-header.scss +++ b/src/lib/expansion/expansion-panel-header.scss @@ -1,6 +1,5 @@ .mat-expansion-panel-header { - cursor: pointer; display: flex; flex-direction: row; align-items: center; @@ -15,6 +14,10 @@ &.mat-expanded:hover, { background: inherit; } + + &:not([aria-disabled='true']) { + cursor: pointer; + } } .mat-content { diff --git a/src/lib/expansion/expansion-panel-header.ts b/src/lib/expansion/expansion-panel-header.ts index 7f9d8ef96b74..2de7617f3851 100644 --- a/src/lib/expansion/expansion-panel-header.ts +++ b/src/lib/expansion/expansion-panel-header.ts @@ -49,9 +49,10 @@ import {Subscription} from 'rxjs/Subscription'; host: { 'class': 'mat-expansion-panel-header', 'role': 'button', - 'tabindex': '0', + '[attr.tabindex]': 'panel.disabled ? -1 : 0', '[attr.aria-controls]': '_getPanelId()', '[attr.aria-expanded]': '_isExpanded()', + '[attr.aria-disabled]': 'panel.disabled', '[class.mat-expanded]': '_isExpanded()', '(click)': '_toggle()', '(keyup)': '_keyup($event)', @@ -85,7 +86,7 @@ export class MdExpansionPanelHeader implements OnDestroy { this._parentChangeSubscription = merge( panel.opened, panel.closed, - filter.call(panel._inputChanges, changes => !!changes.hideToggle) + filter.call(panel._inputChanges, changes => !!(changes.hideToggle || changes.disabled)) ) .subscribe(() => this._changeDetectorRef.markForCheck()); @@ -94,7 +95,9 @@ export class MdExpansionPanelHeader implements OnDestroy { /** Toggles the expanded state of the panel. */ _toggle(): void { - this.panel.toggle(); + if (!this.panel.disabled) { + this.panel.toggle(); + } } /** Gets whether the panel is expanded. */ @@ -112,9 +115,9 @@ export class MdExpansionPanelHeader implements OnDestroy { return this.panel.id; } - /** Gets whether the expand indicator is hidden. */ - _getHideToggle(): boolean { - return this.panel.hideToggle; + /** Gets whether the expand indicator should be shown. */ + _showToggle(): boolean { + return !this.panel.hideToggle && !this.panel.disabled; } /** Handle keyup event calling to toggle() if appropriate. */ diff --git a/src/lib/expansion/expansion-panel.ts b/src/lib/expansion/expansion-panel.ts index b36b8f7e9840..d545e99f3d9e 100644 --- a/src/lib/expansion/expansion-panel.ts +++ b/src/lib/expansion/expansion-panel.ts @@ -54,6 +54,7 @@ export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2, templateUrl: './expansion-panel.html', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, + inputs: ['disabled'], host: { 'class': 'mat-expansion-panel', '[class.mat-expanded]': 'expanded', diff --git a/src/lib/expansion/expansion.md b/src/lib/expansion/expansion.md index fff3acc28d8a..c228a9784b36 100644 --- a/src/lib/expansion/expansion.md +++ b/src/lib/expansion/expansion.md @@ -14,7 +14,7 @@ as the control for expanding and collapsing. This header may optionally contain header to align with Material Design specifications. By default, the expansion-panel header includes a toggle icon at the end of the -header to indicate the expansion state. This icon can be hidden via the +header to indicate the expansion state. This icon can be hidden via the `hideToggle` property. ```html @@ -35,8 +35,8 @@ header to indicate the expansion state. This icon can be hidden via the #### Action bar -Actions may optionally be included at the bottom of the panel, visible only when the expansion is in its -expanded state. +Actions may optionally be included at the bottom of the panel, visible only when the expansion +is in its expanded state. ```html @@ -52,6 +52,23 @@ expanded state. ``` +#### Disabling a panel + +Expansion panels can be disabled using the `disabled` attribute. A disabled expansion panel can't +be toggled by the user, but can still be manipulated using programmatically. + +```html + + + This is the expansion title + + + This is a summary of the content + + +``` + + ### Accordion Multiple expansion-panels can be combined into an accordion. The `multi="true"` input allows the @@ -60,23 +77,23 @@ panel can be expanded at a given time: ```html - + This is the expansion 1 title - + This the expansion 1 content - + - + This is the expansion 2 title - + This the expansion 2 content - + diff --git a/src/lib/expansion/expansion.spec.ts b/src/lib/expansion/expansion.spec.ts index e43cf3f323b1..5c0033a326e4 100644 --- a/src/lib/expansion/expansion.spec.ts +++ b/src/lib/expansion/expansion.spec.ts @@ -1,8 +1,8 @@ -import {async, TestBed, fakeAsync, tick} from '@angular/core/testing'; -import {Component} from '@angular/core'; +import {async, TestBed, fakeAsync, tick, ComponentFixture} from '@angular/core/testing'; +import {Component, ViewChild} from '@angular/core'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {MdExpansionModule} from './index'; +import {MdExpansionModule, MdExpansionPanel} from './index'; describe('MdExpansionPanel', () => { @@ -137,6 +137,66 @@ describe('MdExpansionPanel', () => { expect(arrow.style.transform).toBe('rotate(180deg)', 'Expected 180 degree rotation.'); })); + + describe('disabled state', () => { + let fixture: ComponentFixture; + let panel: HTMLElement; + let header: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(PanelWithContent); + fixture.detectChanges(); + panel = fixture.debugElement.query(By.css('md-expansion-panel')).nativeElement; + header = fixture.debugElement.query(By.css('md-expansion-panel-header')).nativeElement; + }); + + it('should toggle the aria-disabled attribute on the header', () => { + expect(header.getAttribute('aria-disabled')).toBe('false'); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + expect(header.getAttribute('aria-disabled')).toBe('true'); + }); + + it('should toggle the expansion indicator', () => { + expect(panel.querySelector('.mat-expansion-indicator')).toBeTruthy(); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + expect(panel.querySelector('.mat-expansion-indicator')).toBeFalsy(); + }); + + it('should not be able to toggle the panel via a user action if disabled', () => { + expect(fixture.componentInstance.panel.expanded).toBe(false); + expect(header.classList).not.toContain('mat-expanded'); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + header.click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.panel.expanded).toBe(false); + expect(header.classList).not.toContain('mat-expanded'); + }); + + it('should be able to toggle a disabled expansion panel programmatically', () => { + expect(fixture.componentInstance.panel.expanded).toBe(false); + expect(header.classList).not.toContain('mat-expanded'); + + fixture.componentInstance.disabled = true; + fixture.detectChanges(); + + fixture.componentInstance.expanded = true; + fixture.detectChanges(); + + expect(fixture.componentInstance.panel.expanded).toBe(true); + expect(header.classList).toContain('mat-expanded'); + }); + + }); }); @@ -144,6 +204,7 @@ describe('MdExpansionPanel', () => { template: ` Panel Title @@ -152,10 +213,12 @@ describe('MdExpansionPanel', () => { ` }) class PanelWithContent { - expanded: boolean = false; - hideToggle: boolean = false; + expanded = false; + hideToggle = false; + disabled = false; openCallback = jasmine.createSpy('openCallback'); closeCallback = jasmine.createSpy('closeCallback'); + @ViewChild(MdExpansionPanel) panel: MdExpansionPanel; }