Skip to content

Commit c3eac17

Browse files
josephperrottmmalerba
authored andcommitted
feat(expansion): allow expansion toggle indicator positioning (#16257)
1 parent f61730a commit c3eac17

File tree

9 files changed

+94
-7
lines changed

9 files changed

+94
-7
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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ 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
@@ -23,6 +26,9 @@ export interface MatAccordionBase extends CdkAccordion {
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
],
@@ -137,6 +138,22 @@ describe('MatAccordion', () => {
137138
.toBeFalsy('Expected the expansion indicator to be removed.');
138139
});
139140

141+
it('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+
});
156+
140157
it('should move focus to the next header when pressing the down arrow', () => {
141158
const fixture = TestBed.createComponent(SetOfItems);
142159
fixture.detectChanges();
@@ -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: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ 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 {
15+
MAT_ACCORDION,
16+
MatAccordionBase,
17+
MatAccordionDisplayMode,
18+
MatAccordionTogglePosition
19+
} from './accordion-base';
1520
import {MatExpansionPanelHeader} from './expansion-panel-header';
1621

1722
/**
@@ -51,6 +56,9 @@ export class MatAccordion extends CdkAccordion implements MatAccordionBase, Afte
5156
*/
5257
@Input() displayMode: MatAccordionDisplayMode = 'default';
5358

59+
/** The position of the expansion indicator. */
60+
@Input() togglePosition: MatAccordionTogglePosition = 'after';
61+
5462
ngAfterContentInit() {
5563
this._keyManager = new FocusKeyManager(this._headers).withWrap();
5664
}

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

Lines changed: 8 additions & 0 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 {

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

Lines changed: 15 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,12 @@ 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 => {
95+
return !!(
96+
changes['hideToggle'] ||
97+
changes['disabled'] ||
98+
changes['togglePosition']);
99+
})))
92100
.subscribe(() => this._changeDetectorRef.markForCheck());
93101

94102
// Avoids focus being lost if the panel contained the focused element and was closed.
@@ -142,6 +150,11 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption {
142150
return this.panel.id;
143151
}
144152

153+
/** Gets the toggle position for the header. */
154+
_getTogglePosition(): MatAccordionTogglePosition {
155+
return this.panel.togglePosition;
156+
}
157+
145158
/** Gets whether the expand indicator should be shown. */
146159
_showToggle(): boolean {
147160
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>();

tools/public_api_guard/material/expansion.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export declare class MatAccordion extends CdkAccordion implements MatAccordionBa
88
_headers: QueryList<MatExpansionPanelHeader>;
99
displayMode: MatAccordionDisplayMode;
1010
hideToggle: boolean;
11+
togglePosition: MatAccordionTogglePosition;
1112
_handleHeaderFocus(header: MatExpansionPanelHeader): void;
1213
_handleHeaderKeydown(event: KeyboardEvent): void;
1314
ngAfterContentInit(): void;
@@ -18,10 +19,13 @@ export interface MatAccordionBase extends CdkAccordion {
1819
_handleHeaderKeydown: (event: KeyboardEvent) => void;
1920
displayMode: MatAccordionDisplayMode;
2021
hideToggle: boolean;
22+
togglePosition: MatAccordionTogglePosition;
2123
}
2224

2325
export declare type MatAccordionDisplayMode = 'default' | 'flat';
2426

27+
export declare type MatAccordionTogglePosition = 'before' | 'after';
28+
2529
export declare const matExpansionAnimations: {
2630
readonly indicatorRotate: AnimationTriggerMetadata;
2731
readonly expansionHeaderHeight: AnimationTriggerMetadata;
@@ -43,6 +47,7 @@ export declare class MatExpansionPanel extends CdkAccordionItem implements After
4347
afterCollapse: EventEmitter<void>;
4448
afterExpand: EventEmitter<void>;
4549
hideToggle: boolean;
50+
togglePosition: MatAccordionTogglePosition;
4651
constructor(accordion: MatAccordionBase, _changeDetectorRef: ChangeDetectorRef, _uniqueSelectionDispatcher: UniqueSelectionDispatcher, _viewContainerRef: ViewContainerRef, _document: any, _animationMode: string, defaultOptions?: MatExpansionPanelDefaultOptions);
4752
_containsFocus(): boolean;
4853
_getExpandedState(): MatExpansionPanelState;
@@ -77,6 +82,7 @@ export declare class MatExpansionPanelHeader implements OnDestroy, FocusableOpti
7782
constructor(panel: MatExpansionPanel, _element: ElementRef, _focusMonitor: FocusMonitor, _changeDetectorRef: ChangeDetectorRef, defaultOptions?: MatExpansionPanelDefaultOptions);
7883
_getExpandedState(): string;
7984
_getPanelId(): string;
85+
_getTogglePosition(): MatAccordionTogglePosition;
8086
_isExpanded(): boolean;
8187
_keydown(event: KeyboardEvent): void;
8288
_showToggle(): boolean;

0 commit comments

Comments
 (0)