Skip to content

feat(expansion): allow expansion indicator positioning. #8199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/demo-app/expansion/expansion-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ <h1>matAccordion</h1>
<mat-radio-button value="default">Default</mat-radio-button>
<mat-radio-button value="flat">Flat</mat-radio-button>
</mat-radio-group>
<p>Toggle Position</p>
<mat-radio-group [(ngModel)]="togglePosition">
<mat-radio-button value="before">Before</mat-radio-button>
<mat-radio-button value="after">After</mat-radio-button>
</mat-radio-group>
<p>Accordion Actions <sup>('Multi Expansion' mode only)</sup></p>
<div>
<button mat-button (click)="accordion.openAll()" [disabled]="!multi">Expand All</button>
Expand All @@ -55,8 +60,8 @@ <h1>matAccordion</h1>
</div>
</div>
<br>
<mat-accordion [displayMode]="displayMode" [multi]="multi"
class="demo-expansion-width">
<mat-accordion [displayMode]="displayMode" [multi]="multi" [togglePosition]="togglePosition"
class="demo-expansion-width">
<mat-expansion-panel #panel1 [hideToggle]="hideToggle">
<mat-expansion-panel-header>Section 1</mat-expansion-panel-header>
<p>This is the content text that makes sense here.</p>
Expand All @@ -75,7 +80,7 @@ <h1>matAccordion</h1>
</mat-expansion-panel>
</mat-accordion>

<h1>cdkAccordion</h1>
<h1>CdkAccordion</h1>
<div>
<p>Accordion Options</p>
<div>
Expand Down Expand Up @@ -108,4 +113,3 @@ <h1>cdkAccordion</h1>
<p *ngIf="item3.expanded">I only show if item 3 is expanded</p>
</cdk-accordion-item>
</cdk-accordion>

1 change: 1 addition & 0 deletions src/demo-app/expansion/expansion-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class ExpansionDemo {
@ViewChild(MatAccordion) accordion: MatAccordion;

displayMode = 'default';
togglePosition = 'after';
multi = false;
hideToggle = false;
disabled = false;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/expansion/_expansion-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
}

.mat-expansion-panel-header-description,
.mat-expansion-indicator::after {
.mat-expansion-indicator {
color: mat-color($foreground, secondary-text);
}

Expand Down
33 changes: 31 additions & 2 deletions src/lib/expansion/accordion.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MatExpansionModule, MatAccordion} from './index';


describe('CdkAccordion', () => {
describe('MatAccordion', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
Expand Down Expand Up @@ -83,12 +83,39 @@ describe('CdkAccordion', () => {
fixture.detectChanges();
expect(panels[0].classes['mat-expanded']).toBeFalsy();
expect(panels[1].classes['mat-expanded']).toBeFalsy();

});

it('should correctly apply the displayMode provided', () => {
const fixture = TestBed.createComponent(SetOfItems);
const firstPanel = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'))[0];

fixture.componentInstance.firstPanelExpanded = true;
fixture.detectChanges();
expect(firstPanel.classes['mat-expansion-panel-spacing']).toBeTruthy();

fixture.componentInstance.displayMode = 'flat';
fixture.detectChanges();
expect(firstPanel.classes['mat-expansion-panel-spacing']).toBeFalsy();
});

it('should correctly apply the togglePosition provided', () => {
const fixture = TestBed.createComponent(SetOfItems);
fixture.detectChanges();
const firstPanelHeader =
fixture.debugElement.queryAll(By.css('.mat-expansion-indicator-container'))[0];

expect(firstPanelHeader.classes['mat-expansion-indicator-container-before']).toBeFalsy();

fixture.componentInstance.togglePosition = 'before';
fixture.detectChanges();
expect(firstPanelHeader.classes['mat-expansion-indicator-container-before']).toBeTruthy();
});
});


@Component({template: `
<mat-accordion [multi]="multi">
<mat-accordion [multi]="multi" [displayMode]="displayMode" [togglePosition]="togglePosition">
<mat-expansion-panel [expanded]="firstPanelExpanded">
<mat-expansion-panel-header>Summary</mat-expansion-panel-header>
<p>Content</p>
Expand All @@ -102,6 +129,8 @@ class SetOfItems {
@ViewChild(MatAccordion) accordion: MatAccordion;

multi: boolean = false;
displayMode = 'default';
togglePosition = 'after';
firstPanelExpanded: boolean = false;
secondPanelExpanded: boolean = false;
secondPanelDisabled: boolean = false;
Expand Down
21 changes: 20 additions & 1 deletion src/lib/expansion/accordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, Input} from '@angular/core';
import {Directive, Input, SimpleChanges} from '@angular/core';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {CdkAccordion} from '@angular/cdk/accordion';
import {Subject} from 'rxjs';

/** MatAccordion's display modes. */
export type MatAccordionDisplayMode = 'default' | 'flat';


/** MatAccordion's toggle positions. */
export type MatAccordionTogglePosition = 'before' | 'after';

/**
* Directive for a Material Design Accordion.
*/
Expand All @@ -24,6 +29,9 @@ export type MatAccordionDisplayMode = 'default' | 'flat';
}
})
export class MatAccordion extends CdkAccordion {
/** Stream that emits for changes in `@Input` properties. */
_inputChanges = new Subject<SimpleChanges>();

/** Whether the expansion indicator should be hidden. */
@Input()
get hideToggle(): boolean { return this._hideToggle; }
Expand All @@ -39,4 +47,15 @@ export class MatAccordion extends CdkAccordion {
* elevation.
*/
@Input() displayMode: MatAccordionDisplayMode = 'default';

/** The positioning of the expansion indicator. */
@Input() togglePosition: MatAccordionTogglePosition = 'after';

ngOnChanges(changes: SimpleChanges) {
this._inputChanges.next(changes);
}

ngOnDestroy() {
this._inputChanges.complete();
}
}
4 changes: 2 additions & 2 deletions src/lib/expansion/expansion-animations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const matExpansionAnimations: {
} = {
/** Animation that rotates the indicator arrow. */
indicatorRotate: trigger('indicatorRotate', [
state('collapsed', style({transform: 'rotate(0deg)'})),
state('expanded', style({transform: 'rotate(180deg)'})),
state('collapsed', style({transform: 'rotate(45deg)'})),
state('expanded', style({transform: 'rotate(225deg)'})),
transition('expanded <=> collapsed', animate(EXPANSION_PANEL_ANIMATION_TIMING)),
]),

Expand Down
7 changes: 5 additions & 2 deletions src/lib/expansion/expansion-panel-header.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
<ng-content select="mat-panel-description"></ng-content>
<ng-content></ng-content>
</span>
<span [@indicatorRotate]="_getExpandedState()" *ngIf="_showToggle()"
class="mat-expansion-indicator"></span>
<div class="mat-expansion-indicator-container"
[class.mat-expansion-indicator-container-before]="_placeToggleBefore()">
<span *ngIf="_isToggleVisible()" class="mat-expansion-indicator"
[@indicatorRotate]="_getExpandedState()"></span>
</div>
44 changes: 31 additions & 13 deletions src/lib/expansion/expansion-panel-header.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@

.mat-expansion-panel-header {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 24px;
padding: 0 16px;

&:focus,
&:hover {
Expand All @@ -25,12 +24,13 @@
flex: 1;
flex-direction: row;
overflow: hidden;
padding-left: 8px;
}

.mat-expansion-panel-header-title,
.mat-expansion-panel-header-description {
display: flex;
flex-grow: 1;
flex: 1;
margin-right: 16px;

[dir='rtl'] & {
Expand All @@ -40,19 +40,37 @@
}

.mat-expansion-panel-header-description {
flex-grow: 2;
flex: 2;
}

.mat-expansion-indicator-container {
// A margin is required to offset the entire expansion indicator against the space the arrow
// takes up. It is calculated as sqrt(2 * border-width ^ 2) / 2.
margin-bottom: 1.41px;
padding: 0 8px 0 0;
width: 8px;
order: 1;

&.mat-expansion-indicator-container-before {
order: -1;
padding: 0 8px;
}
}

/**
* Creates the expansion indicator arrow. Done using ::after rather than having
* additional nodes in the template.
*/
.mat-expansion-indicator::after {
.mat-expansion-indicator {
border-style: solid;
border-width: 0 2px 2px 0;
content: '';
display: inline-block;
padding: 3px;
display: block;
height: 6px;
transform: rotate(45deg);
vertical-align: middle;
// The transform origin is set by determining the center pointer of the arrow created. It is
// calculated as by calculating the length of the line between the top left corner of the div,
// and the centroid of the triangle created in the bottom right half of the div. This centroid
// is calculated for both X and Y (as the indicator is a square) as
// (indicator-width + indicator-height + 0) / 3
// The length between the resulting coordinates and the top left (0, 0) of the div are
// calculated sqrt((centroids-x-coord ^ 2) + (centroids-y-coord ^ 2))
// This value is used to transform the origin on both the X and Y axes.
transform-origin: 5.65px 5.65px;
width: 6px;
}
20 changes: 17 additions & 3 deletions src/lib/expansion/expansion-panel-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import {
ElementRef,
Host,
Input,
Optional,
OnDestroy,
ViewEncapsulation,
} from '@angular/core';
import {merge, Subscription} from 'rxjs';
import {filter} from 'rxjs/operators';
import {matExpansionAnimations} from './expansion-animations';
import {MatExpansionPanel} from './expansion-panel';
import {MatAccordion} from './accordion';


/**
Expand Down Expand Up @@ -65,17 +67,24 @@ export class MatExpansionPanelHeader implements OnDestroy {
private _parentChangeSubscription = Subscription.EMPTY;

constructor(
@Optional() accordion: MatAccordion,
@Host() public panel: MatExpansionPanel,
private _element: ElementRef,
private _focusMonitor: FocusMonitor,
private _changeDetectorRef: ChangeDetectorRef) {

let changeStreams = [panel._inputChanges];
if (accordion) {
changeStreams.push(accordion._inputChanges);
}

// Since the toggle state depends on an @Input on the panel, we
// need to subscribe and trigger change detection manually.
// need to subscribe and trigger change detection manually.
this._parentChangeSubscription = merge(
panel.opened,
panel.closed,
panel._inputChanges.pipe(filter(changes => !!(changes.hideToggle || changes.disabled)))
merge(...changeStreams).pipe(
filter(changes => !!(changes.hideToggle || changes.disabled || changes.togglePosition)))
)
.subscribe(() => this._changeDetectorRef.markForCheck());

Expand Down Expand Up @@ -109,10 +118,15 @@ export class MatExpansionPanelHeader implements OnDestroy {
}

/** Gets whether the expand indicator should be shown. */
_showToggle(): boolean {
_isToggleVisible(): boolean {
return !this.panel.hideToggle && !this.panel.disabled;
}

/** Whether the expand indicator should be shown before the header content */
_placeToggleBefore(): boolean {
return this.panel.togglePosition === 'before';
}

/** Handle keydown event calling to toggle() if appropriate. */
_keydown(event: KeyboardEvent) {
switch (event.keyCode) {
Expand Down
14 changes: 12 additions & 2 deletions src/lib/expansion/expansion-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import {
} from '@angular/core';
import {Subject} from 'rxjs';
import {filter, startWith, take} from 'rxjs/operators';
import {MatAccordion} from './accordion';
import {matExpansionAnimations} from './expansion-animations';
import {MatAccordion, MatAccordionTogglePosition} from './accordion';
import {MatExpansionPanelContent} from './expansion-panel-content';
import {matExpansionAnimations} from './expansion-animations';


/** MatExpansionPanel's states. */
Expand Down Expand Up @@ -72,6 +72,16 @@ export class MatExpansionPanel extends CdkAccordionItem
}
private _hideToggle = false;

/** The positioning of the expansion indicator. */
@Input()
get togglePosition(): MatAccordionTogglePosition {
return this.accordion ? this.accordion.togglePosition : this._togglePosition;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the more specific position trump the parent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the hideToggle value goes based on parent's value even if the child is defined so I went with the same. There is a strong argument that they both should be changed.

}
set togglePosition(position: MatAccordionTogglePosition) {
this._togglePosition = position;
}
private _togglePosition: MatAccordionTogglePosition = 'after';

/** Stream that emits for changes in `@Input` properties. */
readonly _inputChanges = new Subject<SimpleChanges>();

Expand Down
3 changes: 2 additions & 1 deletion src/lib/expansion/expansion.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ 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
`hideToggle` property.
`hideToggle` property. The icon's position can also be configured with the `togglePosition`
property.

```html
<mat-expansion-panel>
Expand Down
Loading