Skip to content

Commit 921432a

Browse files
crisbetokara
authored andcommitted
feat(expansion-panel): add the ability to disable an expansion panel (#6529)
Fixes #6521.
1 parent 449ed19 commit 921432a

10 files changed

+135
-28
lines changed

src/demo-app/expansion/expansion-demo.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ <h1>Accordion</h1>
1717
<div>
1818
<md-slide-toggle [(ngModel)]="multi">Allow Multi Expansion</md-slide-toggle>
1919
<md-slide-toggle [(ngModel)]="hideToggle">Hide Indicators</md-slide-toggle>
20+
<md-slide-toggle [(ngModel)]="disabled">Disable Panel 2</md-slide-toggle>
2021
<md-slide-toggle [(ngModel)]="showPanel3">Show Panel 3</md-slide-toggle>
2122
</div>
2223
<p>Accordion Style</p>
@@ -37,7 +38,7 @@ <h1>Accordion</h1>
3738
<md-expansion-panel-header>Section 1</md-expansion-panel-header>
3839
<p>This is the content text that makes sense here.</p>
3940
</md-expansion-panel>
40-
<md-expansion-panel #panel2 [hideToggle]="hideToggle">
41+
<md-expansion-panel #panel2 [hideToggle]="hideToggle" [disabled]="disabled">
4142
<md-expansion-panel-header>Section 2</md-expansion-panel-header>
4243
<p>This is the content text that makes sense here.</p>
4344
</md-expansion-panel>
@@ -49,4 +50,4 @@ <h1>Accordion</h1>
4950
<button md-button (click)="panel3.expanded = false">CLOSE</button>
5051
</md-action-row>
5152
</md-expansion-panel>
52-
</md-accordion>
53+
</md-accordion>

src/demo-app/expansion/expansion-demo.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {Component, ViewEncapsulation} from '@angular/core';
99
})
1010
export class ExpansionDemo {
1111
displayMode: string = 'default';
12-
multi: boolean = false;
13-
hideToggle: boolean = false;
12+
multi = false;
13+
hideToggle = false;
14+
disabled = false;
1415
showPanel3 = true;
1516
}

src/lib/expansion/_expansion-theme.scss

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
border-top-color: mat-color($foreground, divider);
1616
}
1717

18-
.mat-expansion-panel-header {
18+
.mat-expansion-panel-header:not([aria-disabled='true']) {
1919
&.cdk-keyboard-focused,
2020
&.cdk-program-focused,
2121
&:hover {
@@ -31,6 +31,15 @@
3131
.mat-expansion-indicator::after {
3232
color: mat-color($foreground, secondary-text);
3333
}
34+
35+
.mat-expansion-panel-header[aria-disabled='true'] {
36+
color: mat-color($foreground, disabled-button);
37+
38+
.mat-expansion-panel-header-title,
39+
.mat-expansion-panel-header-description {
40+
color: inherit;
41+
}
42+
}
3443
}
3544

3645
@mixin mat-expansion-panel-typography($config) {

src/lib/expansion/accordion-item.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,22 @@ import {
1717
} from '@angular/core';
1818
import {UniqueSelectionDispatcher} from '../core';
1919
import {CdkAccordion} from './accordion';
20+
import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled';
2021

2122
/** Used to generate unique ID for each expansion panel. */
2223
let nextId = 0;
2324

25+
// Boilerplate for applying mixins to MdSlider.
26+
/** @docs-private */
27+
export class AccordionItemBase { }
28+
export const _AccordionItemMixinBase = mixinDisabled(AccordionItemBase);
29+
2430
/**
2531
* An abstract class to be extended and decorated as a component. Sets up all
2632
* events and attributes needed to be managed by a CdkAccordion parent.
2733
*/
2834
@Injectable()
29-
export class AccordionItem implements OnDestroy {
35+
export class AccordionItem extends _AccordionItemMixinBase implements OnDestroy, CanDisable {
3036
/** Event emitted every time the MdAccordionChild is closed. */
3137
@Output() closed = new EventEmitter<void>();
3238
/** Event emitted every time the MdAccordionChild is opened. */
@@ -68,6 +74,9 @@ export class AccordionItem implements OnDestroy {
6874
constructor(@Optional() public accordion: CdkAccordion,
6975
private _changeDetectorRef: ChangeDetectorRef,
7076
protected _expansionDispatcher: UniqueSelectionDispatcher) {
77+
78+
super();
79+
7180
this._removeUniqueSelectionListener =
7281
_expansionDispatcher.listen((id: string, accordionId: string) => {
7382
if (this.accordion && !this.accordion.multi &&

src/lib/expansion/expansion-panel-header.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
<ng-content select="md-panel-description, mat-panel-description"></ng-content>
44
<ng-content></ng-content>
55
</span>
6-
<span [@indicatorRotate]="_getExpandedState()" *ngIf="!_getHideToggle()"
6+
<span [@indicatorRotate]="_getExpandedState()" *ngIf="_showToggle()"
77
class="mat-expansion-indicator"></span>

src/lib/expansion/expansion-panel-header.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

22
.mat-expansion-panel-header {
3-
cursor: pointer;
43
display: flex;
54
flex-direction: row;
65
align-items: center;
@@ -15,6 +14,10 @@
1514
&.mat-expanded:hover, {
1615
background: inherit;
1716
}
17+
18+
&:not([aria-disabled='true']) {
19+
cursor: pointer;
20+
}
1821
}
1922

2023
.mat-content {

src/lib/expansion/expansion-panel-header.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ import {Subscription} from 'rxjs/Subscription';
4949
host: {
5050
'class': 'mat-expansion-panel-header',
5151
'role': 'button',
52-
'tabindex': '0',
52+
'[attr.tabindex]': 'panel.disabled ? -1 : 0',
5353
'[attr.aria-controls]': '_getPanelId()',
5454
'[attr.aria-expanded]': '_isExpanded()',
55+
'[attr.aria-disabled]': 'panel.disabled',
5556
'[class.mat-expanded]': '_isExpanded()',
5657
'(click)': '_toggle()',
5758
'(keyup)': '_keyup($event)',
@@ -85,7 +86,7 @@ export class MdExpansionPanelHeader implements OnDestroy {
8586
this._parentChangeSubscription = merge(
8687
panel.opened,
8788
panel.closed,
88-
filter.call(panel._inputChanges, changes => !!changes.hideToggle)
89+
filter.call(panel._inputChanges, changes => !!(changes.hideToggle || changes.disabled))
8990
)
9091
.subscribe(() => this._changeDetectorRef.markForCheck());
9192

@@ -94,7 +95,9 @@ export class MdExpansionPanelHeader implements OnDestroy {
9495

9596
/** Toggles the expanded state of the panel. */
9697
_toggle(): void {
97-
this.panel.toggle();
98+
if (!this.panel.disabled) {
99+
this.panel.toggle();
100+
}
98101
}
99102

100103
/** Gets whether the panel is expanded. */
@@ -112,9 +115,9 @@ export class MdExpansionPanelHeader implements OnDestroy {
112115
return this.panel.id;
113116
}
114117

115-
/** Gets whether the expand indicator is hidden. */
116-
_getHideToggle(): boolean {
117-
return this.panel.hideToggle;
118+
/** Gets whether the expand indicator should be shown. */
119+
_showToggle(): boolean {
120+
return !this.panel.hideToggle && !this.panel.disabled;
118121
}
119122

120123
/** Handle keyup event calling to toggle() if appropriate. */

src/lib/expansion/expansion-panel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2,
5454
templateUrl: './expansion-panel.html',
5555
encapsulation: ViewEncapsulation.None,
5656
changeDetection: ChangeDetectionStrategy.OnPush,
57+
inputs: ['disabled'],
5758
host: {
5859
'class': 'mat-expansion-panel',
5960
'[class.mat-expanded]': 'expanded',

src/lib/expansion/expansion.md

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ as the control for expanding and collapsing. This header may optionally contain
1414
header to align with Material Design specifications.
1515

1616
By default, the expansion-panel header includes a toggle icon at the end of the
17-
header to indicate the expansion state. This icon can be hidden via the
17+
header to indicate the expansion state. This icon can be hidden via the
1818
`hideToggle` property.
1919

2020
```html
@@ -35,8 +35,8 @@ header to indicate the expansion state. This icon can be hidden via the
3535

3636
#### Action bar
3737

38-
Actions may optionally be included at the bottom of the panel, visible only when the expansion is in its
39-
expanded state.
38+
Actions may optionally be included at the bottom of the panel, visible only when the expansion
39+
is in its expanded state.
4040

4141
```html
4242
<md-expansion-panel>
@@ -52,6 +52,23 @@ expanded state.
5252
</md-expansion-panel>
5353
```
5454

55+
#### Disabling a panel
56+
57+
Expansion panels can be disabled using the `disabled` attribute. A disabled expansion panel can't
58+
be toggled by the user, but can still be manipulated using programmatically.
59+
60+
```html
61+
<md-expansion-panel [disabled]="isDisabled">
62+
<md-expansion-panel-header>
63+
This is the expansion title
64+
</md-expansion-panel-header>
65+
<md-panel-description>
66+
This is a summary of the content
67+
</md-panel-description>
68+
</md-expansion-panel>
69+
```
70+
71+
5572
### Accordion
5673

5774
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:
6077

6178
```html
6279
<md-accordion>
63-
80+
6481
<md-expansion-panel>
6582
<md-expansion-panel-header>
6683
This is the expansion 1 title
6784
</md-expansion-panel-header>
68-
85+
6986
This the expansion 1 content
70-
87+
7188
</md-expansion-panel>
72-
89+
7390
<md-expansion-panel>
7491
<md-expansion-panel-header>
7592
This is the expansion 2 title
7693
</md-expansion-panel-header>
77-
94+
7895
This the expansion 2 content
79-
96+
8097
</md-expansion-panel>
8198

8299
</md-accordion>

src/lib/expansion/expansion.spec.ts

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {async, TestBed, fakeAsync, tick} from '@angular/core/testing';
2-
import {Component} from '@angular/core';
1+
import {async, TestBed, fakeAsync, tick, ComponentFixture} from '@angular/core/testing';
2+
import {Component, ViewChild} from '@angular/core';
33
import {By} from '@angular/platform-browser';
44
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
5-
import {MdExpansionModule} from './index';
5+
import {MdExpansionModule, MdExpansionPanel} from './index';
66

77

88
describe('MdExpansionPanel', () => {
@@ -137,13 +137,74 @@ describe('MdExpansionPanel', () => {
137137

138138
expect(arrow.style.transform).toBe('rotate(180deg)', 'Expected 180 degree rotation.');
139139
}));
140+
141+
describe('disabled state', () => {
142+
let fixture: ComponentFixture<PanelWithContent>;
143+
let panel: HTMLElement;
144+
let header: HTMLElement;
145+
146+
beforeEach(() => {
147+
fixture = TestBed.createComponent(PanelWithContent);
148+
fixture.detectChanges();
149+
panel = fixture.debugElement.query(By.css('md-expansion-panel')).nativeElement;
150+
header = fixture.debugElement.query(By.css('md-expansion-panel-header')).nativeElement;
151+
});
152+
153+
it('should toggle the aria-disabled attribute on the header', () => {
154+
expect(header.getAttribute('aria-disabled')).toBe('false');
155+
156+
fixture.componentInstance.disabled = true;
157+
fixture.detectChanges();
158+
159+
expect(header.getAttribute('aria-disabled')).toBe('true');
160+
});
161+
162+
it('should toggle the expansion indicator', () => {
163+
expect(panel.querySelector('.mat-expansion-indicator')).toBeTruthy();
164+
165+
fixture.componentInstance.disabled = true;
166+
fixture.detectChanges();
167+
168+
expect(panel.querySelector('.mat-expansion-indicator')).toBeFalsy();
169+
});
170+
171+
it('should not be able to toggle the panel via a user action if disabled', () => {
172+
expect(fixture.componentInstance.panel.expanded).toBe(false);
173+
expect(header.classList).not.toContain('mat-expanded');
174+
175+
fixture.componentInstance.disabled = true;
176+
fixture.detectChanges();
177+
178+
header.click();
179+
fixture.detectChanges();
180+
181+
expect(fixture.componentInstance.panel.expanded).toBe(false);
182+
expect(header.classList).not.toContain('mat-expanded');
183+
});
184+
185+
it('should be able to toggle a disabled expansion panel programmatically', () => {
186+
expect(fixture.componentInstance.panel.expanded).toBe(false);
187+
expect(header.classList).not.toContain('mat-expanded');
188+
189+
fixture.componentInstance.disabled = true;
190+
fixture.detectChanges();
191+
192+
fixture.componentInstance.expanded = true;
193+
fixture.detectChanges();
194+
195+
expect(fixture.componentInstance.panel.expanded).toBe(true);
196+
expect(header.classList).toContain('mat-expanded');
197+
});
198+
199+
});
140200
});
141201

142202

143203
@Component({
144204
template: `
145205
<md-expansion-panel [expanded]="expanded"
146206
[hideToggle]="hideToggle"
207+
[disabled]="disabled"
147208
(opened)="openCallback()"
148209
(closed)="closeCallback()">
149210
<md-expansion-panel-header>Panel Title</md-expansion-panel-header>
@@ -152,10 +213,12 @@ describe('MdExpansionPanel', () => {
152213
</md-expansion-panel>`
153214
})
154215
class PanelWithContent {
155-
expanded: boolean = false;
156-
hideToggle: boolean = false;
216+
expanded = false;
217+
hideToggle = false;
218+
disabled = false;
157219
openCallback = jasmine.createSpy('openCallback');
158220
closeCallback = jasmine.createSpy('closeCallback');
221+
@ViewChild(MdExpansionPanel) panel: MdExpansionPanel;
159222
}
160223

161224

0 commit comments

Comments
 (0)