Skip to content

Commit 8f84f1e

Browse files
committed
feat(expansion): allow expansion toggle indivicator positioning
1 parent c1e07ad commit 8f84f1e

File tree

8 files changed

+92
-17
lines changed

8 files changed

+92
-17
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ <h1>matAccordion</h1>
5050
<mat-radio-button value="default">Default</mat-radio-button>
5151
<mat-radio-button value="flat">Flat</mat-radio-button>
5252
</mat-radio-group>
53+
54+
<p>Toggle Position</p>
55+
<mat-radio-group [(ngModel)]="togglePosition">
56+
<mat-radio-button value="before">Before</mat-radio-button>
57+
<mat-radio-button value="after">After</mat-radio-button>
58+
</mat-radio-group>
5359
<p>Accordion Actions <sup>('Multi Expansion' mode only)</sup></p>
5460
<div>
5561
<button mat-button (click)="accordion.openAll()" [disabled]="!multi">Expand All</button>
@@ -63,7 +69,7 @@ <h1>matAccordion</h1>
6369
</div>
6470
<br>
6571
<mat-accordion [displayMode]="displayMode" [multi]="multi" [hideToggle]="hideToggle"
66-
class="demo-expansion-width">
72+
[togglePosition]="togglePosition" class="demo-expansion-width">
6773
<mat-expansion-panel #panel1>
6874
<mat-expansion-panel-header>Section 1</mat-expansion-panel-header>
6975
<p>This is the content text that makes sense here.</p>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class ExpansionDemo {
2424
hideToggle = false;
2525
disabled = false;
2626
showPanel3 = true;
27+
togglePosition = 'after';
2728
expandedHeight: string;
2829
collapsedHeight: string;
2930
events: string[] = [];

src/material/expansion/accordion-base.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,23 @@ import {CdkAccordion} from '@angular/cdk/accordion';
1212
/** MatAccordion's display modes. */
1313
export type MatAccordionDisplayMode = 'default' | 'flat';
1414

15+
/** MatAccordion's toggle positions. */
16+
export type MatAccordionTogglePosition = 'before' | 'after';
17+
1518
/**
1619
* Base interface for a `MatAccordion`.
1720
* @docs-private
1821
*/
1922
export interface MatAccordionBase extends CdkAccordion {
2023
/** Whether the expansion indicator should be hidden. */
2124
hideToggle: boolean;
22-
25+
2326
/** Display mode used for all expansion panels in the accordion. */
2427
displayMode: MatAccordionDisplayMode;
2528

29+
/** The position of the expansion indicator. */
30+
togglePosition: MatAccordionTogglePosition;
31+
2632
/** Handles keyboard events coming in from the panel headers. */
2733
_handleHeaderKeydown: (event: KeyboardEvent) => void;
2834

src/material/expansion/accordion.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ describe('MatAccordion', () => {
2424
],
2525
declarations: [
2626
AccordionWithHideToggle,
27+
AccordionWithTogglePosition,
2728
NestedPanel,
2829
SetOfItems,
2930
],
@@ -136,6 +137,22 @@ describe('MatAccordion', () => {
136137
expect(panel.nativeElement.querySelector('.mat-expansion-indicator'))
137138
.toBeFalsy('Expected the expansion indicator to be removed.');
138139
});
140+
141+
fit('should update the expansion panel if togglePosition changed', () => {
142+
const fixture = TestBed.createComponent(AccordionWithTogglePosition);
143+
const panel = fixture.debugElement.query(By.directive(MatExpansionPanel));
144+
145+
fixture.detectChanges();
146+
147+
expect(panel.nativeElement.querySelector('.mat-expansion-toggle-indicator-after'))
148+
.toBeTruthy('Expected the expansion indicator to be positioned after.');
149+
150+
fixture.componentInstance.togglePosition = 'before';
151+
fixture.detectChanges();
152+
153+
expect(panel.nativeElement.querySelector('.mat-expansion-toggle-indicator-before'))
154+
.toBeTruthy('Expected the expansion indicator to be positioned before.');
155+
});
139156

140157
it('should move focus to the next header when pressing the down arrow', () => {
141158
const fixture = TestBed.createComponent(SetOfItems);
@@ -307,3 +324,16 @@ class NestedPanel {
307324
class AccordionWithHideToggle {
308325
hideToggle = false;
309326
}
327+
328+
329+
@Component({template: `
330+
<mat-accordion [togglePosition]="togglePosition">
331+
<mat-expansion-panel>
332+
<mat-expansion-panel-header>Header</mat-expansion-panel-header>
333+
<p>Content</p>
334+
</mat-expansion-panel>
335+
</mat-accordion>`
336+
})
337+
class AccordionWithTogglePosition {
338+
togglePosition = 'after';
339+
}

src/material/expansion/accordion.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {coerceBooleanProperty} from '@angular/cdk/coercion';
1111
import {CdkAccordion} from '@angular/cdk/accordion';
1212
import {FocusKeyManager} from '@angular/cdk/a11y';
1313
import {HOME, END, hasModifierKey} from '@angular/cdk/keycodes';
14-
import {MAT_ACCORDION, MatAccordionBase, MatAccordionDisplayMode} from './accordion-base';
14+
import {MAT_ACCORDION, MatAccordionBase, MatAccordionDisplayMode, MatAccordionTogglePosition} from './accordion-base';
1515
import {MatExpansionPanelHeader} from './expansion-panel-header';
1616

1717
/**
@@ -51,6 +51,9 @@ export class MatAccordion extends CdkAccordion implements MatAccordionBase, Afte
5151
*/
5252
@Input() displayMode: MatAccordionDisplayMode = 'default';
5353

54+
/** The position of the expansion indicator. */
55+
@Input() togglePosition: MatAccordionTogglePosition = 'after';
56+
5457
ngAfterContentInit() {
5558
this._keyManager = new FocusKeyManager(this._headers).withWrap();
5659
}

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

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@
1818
&:not([aria-disabled='true']) {
1919
cursor: pointer;
2020
}
21+
22+
&.mat-expansion-toggle-indicator-before {
23+
flex-direction: row-reverse;
24+
25+
.mat-expansion-indicator {
26+
padding: 0 16px 0 0;
27+
}
28+
}
2129
}
2230

2331
.mat-content {
@@ -47,12 +55,16 @@
4755
* Creates the expansion indicator arrow. Done using ::after rather than having
4856
* additional nodes in the template.
4957
*/
50-
.mat-expansion-indicator::after {
51-
border-style: solid;
52-
border-width: 0 2px 2px 0;
53-
content: '';
54-
display: inline-block;
55-
padding: 3px;
56-
transform: rotate(45deg);
57-
vertical-align: middle;
58-
}
58+
.mat-expansion-indicator {
59+
padding: 0 0 0 16px;
60+
61+
&::after {
62+
border-style: solid;
63+
border-width: 0 2px 2px 0;
64+
content: '';
65+
display: inline-block;
66+
padding: 3px;
67+
transform: rotate(45deg);
68+
vertical-align: middle;
69+
}
70+
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
MatExpansionPanelDefaultOptions,
3030
MAT_EXPANSION_PANEL_DEFAULT_OPTIONS,
3131
} from './expansion-panel';
32+
import {MatAccordionTogglePosition} from './accordion-base';
3233

3334

3435
/**
@@ -56,6 +57,8 @@ import {
5657
'[attr.aria-expanded]': '_isExpanded()',
5758
'[attr.aria-disabled]': 'panel.disabled',
5859
'[class.mat-expanded]': '_isExpanded()',
60+
'[class.mat-expansion-toggle-indicator-after]': `_getTogglePosition() === 'after'`,
61+
'[class.mat-expansion-toggle-indicator-before]': `_getTogglePosition() === 'before'`,
5962
'(click)': '_toggle()',
6063
'(keydown)': '_keydown($event)',
6164
'[@expansionHeight]': `{
@@ -79,7 +82,7 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption {
7982
defaultOptions?: MatExpansionPanelDefaultOptions) {
8083
const accordionHideToggleChange = panel.accordion ?
8184
panel.accordion._stateChanges.pipe(
82-
filter(changes => !!changes['hideToggle'])) :
85+
filter(changes => !!(changes['hideToggle'] || changes['togglePosition']))) :
8386
EMPTY;
8487

8588
// Since the toggle state depends on an @Input on the panel, we
@@ -88,7 +91,7 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption {
8891
merge(
8992
panel.opened, panel.closed, accordionHideToggleChange,
9093
panel._inputChanges.pipe(filter(
91-
changes => !!(changes['hideToggle'] || changes['disabled']))))
94+
changes => !!(changes['hideToggle'] || changes['disabled'] || changes['togglePosition']))))
9295
.subscribe(() => this._changeDetectorRef.markForCheck());
9396

9497
// Avoids focus being lost if the panel contained the focused element and was closed.
@@ -142,6 +145,11 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption {
142145
return this.panel.id;
143146
}
144147

148+
/** Gets the toggle position for the header. */
149+
_getTogglePosition(): MatAccordionTogglePosition {
150+
return this.panel.togglePosition;
151+
}
152+
145153
/** Gets whether the expand indicator should be shown. */
146154
_showToggle(): boolean {
147155
return !this.panel.hideToggle && !this.panel.disabled;

src/material/expansion/expansion-panel.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {Subject} from 'rxjs';
3939
import {filter, startWith, take, distinctUntilChanged} from 'rxjs/operators';
4040
import {matExpansionAnimations} from './expansion-animations';
4141
import {MatExpansionPanelContent} from './expansion-panel-content';
42-
import {MAT_ACCORDION, MatAccordionBase} from './accordion-base';
42+
import {MAT_ACCORDION, MatAccordionBase, MatAccordionTogglePosition} from './accordion-base';
4343

4444
/** MatExpansionPanel's states. */
4545
export type MatExpansionPanelState = 'expanded' | 'collapsed';
@@ -100,8 +100,9 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
100100
})
101101
export class MatExpansionPanel extends CdkAccordionItem implements AfterContentInit, OnChanges,
102102
OnDestroy {
103-
104103
private _document: Document;
104+
private _hideToggle = false;
105+
private _togglePosition: MatAccordionTogglePosition;
105106

106107
/** Whether the toggle indicator should be hidden. */
107108
@Input()
@@ -111,7 +112,15 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI
111112
set hideToggle(value: boolean) {
112113
this._hideToggle = coerceBooleanProperty(value);
113114
}
114-
private _hideToggle = false;
115+
116+
/** Whether the toggle indicator should be hidden. */
117+
@Input()
118+
get togglePosition(): MatAccordionTogglePosition {
119+
return this._togglePosition || (this.accordion && this.accordion.togglePosition);
120+
}
121+
set togglePosition(value: MatAccordionTogglePosition) {
122+
this._togglePosition = value;
123+
}
115124

116125
/** An event emitted after the body's expansion animation happens. */
117126
@Output() afterExpand = new EventEmitter<void>();

0 commit comments

Comments
 (0)