From 85e0aada04b1a30ecd26e9298627ae857e6bebd0 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Mon, 10 Jun 2019 12:30:15 -0700 Subject: [PATCH] feat(expansion): allow expansion toggle indicator positioning --- src/dev-app/expansion/expansion-demo.html | 8 ++++- src/dev-app/expansion/expansion-demo.ts | 1 + src/material/expansion/accordion-base.ts | 6 ++++ src/material/expansion/accordion.spec.ts | 30 +++++++++++++++++++ src/material/expansion/accordion.ts | 10 ++++++- .../expansion/expansion-panel-header.scss | 28 ++++++++++++----- .../expansion/expansion-panel-header.ts | 17 +++++++++-- src/material/expansion/expansion-panel.ts | 15 ++++++++-- .../public_api_guard/material/expansion.d.ts | 6 ++++ 9 files changed, 106 insertions(+), 15 deletions(-) diff --git a/src/dev-app/expansion/expansion-demo.html b/src/dev-app/expansion/expansion-demo.html index 30bf4918a84d..98b14ca6ada2 100644 --- a/src/dev-app/expansion/expansion-demo.html +++ b/src/dev-app/expansion/expansion-demo.html @@ -50,6 +50,12 @@

matAccordion

Default Flat + +

Toggle Position

+ + Before + After +

Accordion Actions ('Multi Expansion' mode only)

@@ -63,7 +69,7 @@

matAccordion


+ [togglePosition]="togglePosition" class="demo-expansion-width"> Section 1

This is the content text that makes sense here.

diff --git a/src/dev-app/expansion/expansion-demo.ts b/src/dev-app/expansion/expansion-demo.ts index 2a54302b4d92..42d289c30e2b 100644 --- a/src/dev-app/expansion/expansion-demo.ts +++ b/src/dev-app/expansion/expansion-demo.ts @@ -24,6 +24,7 @@ export class ExpansionDemo { hideToggle = false; disabled = false; showPanel3 = true; + togglePosition = 'after'; expandedHeight: string; collapsedHeight: string; events: string[] = []; diff --git a/src/material/expansion/accordion-base.ts b/src/material/expansion/accordion-base.ts index 159cde29dcc6..c5b29eae8921 100644 --- a/src/material/expansion/accordion-base.ts +++ b/src/material/expansion/accordion-base.ts @@ -12,6 +12,9 @@ import {CdkAccordion} from '@angular/cdk/accordion'; /** MatAccordion's display modes. */ export type MatAccordionDisplayMode = 'default' | 'flat'; +/** MatAccordion's toggle positions. */ +export type MatAccordionTogglePosition = 'before' | 'after'; + /** * Base interface for a `MatAccordion`. * @docs-private @@ -23,6 +26,9 @@ export interface MatAccordionBase extends CdkAccordion { /** Display mode used for all expansion panels in the accordion. */ displayMode: MatAccordionDisplayMode; + /** The position of the expansion indicator. */ + togglePosition: MatAccordionTogglePosition; + /** Handles keyboard events coming in from the panel headers. */ _handleHeaderKeydown: (event: KeyboardEvent) => void; diff --git a/src/material/expansion/accordion.spec.ts b/src/material/expansion/accordion.spec.ts index 4f0e7433ff2f..4e2f93ede165 100644 --- a/src/material/expansion/accordion.spec.ts +++ b/src/material/expansion/accordion.spec.ts @@ -24,6 +24,7 @@ describe('MatAccordion', () => { ], declarations: [ AccordionWithHideToggle, + AccordionWithTogglePosition, NestedPanel, SetOfItems, ], @@ -137,6 +138,22 @@ describe('MatAccordion', () => { .toBeFalsy('Expected the expansion indicator to be removed.'); }); + it('should update the expansion panel if togglePosition changed', () => { + const fixture = TestBed.createComponent(AccordionWithTogglePosition); + const panel = fixture.debugElement.query(By.directive(MatExpansionPanel)); + + fixture.detectChanges(); + + expect(panel.nativeElement.querySelector('.mat-expansion-toggle-indicator-after')) + .toBeTruthy('Expected the expansion indicator to be positioned after.'); + + fixture.componentInstance.togglePosition = 'before'; + fixture.detectChanges(); + + expect(panel.nativeElement.querySelector('.mat-expansion-toggle-indicator-before')) + .toBeTruthy('Expected the expansion indicator to be positioned before.'); + }); + it('should move focus to the next header when pressing the down arrow', () => { const fixture = TestBed.createComponent(SetOfItems); fixture.detectChanges(); @@ -307,3 +324,16 @@ class NestedPanel { class AccordionWithHideToggle { hideToggle = false; } + + +@Component({template: ` + + + Header +

Content

+
+
` +}) +class AccordionWithTogglePosition { + togglePosition = 'after'; +} diff --git a/src/material/expansion/accordion.ts b/src/material/expansion/accordion.ts index 0a9cb3c1d1f7..f9aa307aba11 100644 --- a/src/material/expansion/accordion.ts +++ b/src/material/expansion/accordion.ts @@ -11,7 +11,12 @@ import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {CdkAccordion} from '@angular/cdk/accordion'; import {FocusKeyManager} from '@angular/cdk/a11y'; import {HOME, END, hasModifierKey} from '@angular/cdk/keycodes'; -import {MAT_ACCORDION, MatAccordionBase, MatAccordionDisplayMode} from './accordion-base'; +import { + MAT_ACCORDION, + MatAccordionBase, + MatAccordionDisplayMode, + MatAccordionTogglePosition +} from './accordion-base'; import {MatExpansionPanelHeader} from './expansion-panel-header'; /** @@ -51,6 +56,9 @@ export class MatAccordion extends CdkAccordion implements MatAccordionBase, Afte */ @Input() displayMode: MatAccordionDisplayMode = 'default'; + /** The position of the expansion indicator. */ + @Input() togglePosition: MatAccordionTogglePosition = 'after'; + ngAfterContentInit() { this._keyManager = new FocusKeyManager(this._headers).withWrap(); } diff --git a/src/material/expansion/expansion-panel-header.scss b/src/material/expansion/expansion-panel-header.scss index d43ecffb4396..3fabac11d311 100644 --- a/src/material/expansion/expansion-panel-header.scss +++ b/src/material/expansion/expansion-panel-header.scss @@ -18,6 +18,14 @@ &:not([aria-disabled='true']) { cursor: pointer; } + + &.mat-expansion-toggle-indicator-before { + flex-direction: row-reverse; + + .mat-expansion-indicator { + padding: 0 16px 0 0; + } + } } .mat-content { @@ -47,12 +55,16 @@ * Creates the expansion indicator arrow. Done using ::after rather than having * additional nodes in the template. */ -.mat-expansion-indicator::after { - border-style: solid; - border-width: 0 2px 2px 0; - content: ''; - display: inline-block; - padding: 3px; - transform: rotate(45deg); - vertical-align: middle; +.mat-expansion-indicator { + padding: 0 0 0 16px; + + &::after { + border-style: solid; + border-width: 0 2px 2px 0; + content: ''; + display: inline-block; + padding: 3px; + transform: rotate(45deg); + vertical-align: middle; + } } diff --git a/src/material/expansion/expansion-panel-header.ts b/src/material/expansion/expansion-panel-header.ts index 6fb800e20452..b04aecf2af62 100644 --- a/src/material/expansion/expansion-panel-header.ts +++ b/src/material/expansion/expansion-panel-header.ts @@ -29,6 +29,7 @@ import { MatExpansionPanelDefaultOptions, MAT_EXPANSION_PANEL_DEFAULT_OPTIONS, } from './expansion-panel'; +import {MatAccordionTogglePosition} from './accordion-base'; /** @@ -56,6 +57,8 @@ import { '[attr.aria-expanded]': '_isExpanded()', '[attr.aria-disabled]': 'panel.disabled', '[class.mat-expanded]': '_isExpanded()', + '[class.mat-expansion-toggle-indicator-after]': `_getTogglePosition() === 'after'`, + '[class.mat-expansion-toggle-indicator-before]': `_getTogglePosition() === 'before'`, '(click)': '_toggle()', '(keydown)': '_keydown($event)', '[@expansionHeight]': `{ @@ -79,7 +82,7 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption { defaultOptions?: MatExpansionPanelDefaultOptions) { const accordionHideToggleChange = panel.accordion ? panel.accordion._stateChanges.pipe( - filter(changes => !!changes['hideToggle'])) : + filter(changes => !!(changes['hideToggle'] || changes['togglePosition']))) : EMPTY; // Since the toggle state depends on an @Input on the panel, we @@ -88,7 +91,12 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption { merge( panel.opened, panel.closed, accordionHideToggleChange, panel._inputChanges.pipe(filter( - changes => !!(changes['hideToggle'] || changes['disabled'])))) + changes => { + return !!( + changes['hideToggle'] || + changes['disabled'] || + changes['togglePosition']); + }))) .subscribe(() => this._changeDetectorRef.markForCheck()); // Avoids focus being lost if the panel contained the focused element and was closed. @@ -142,6 +150,11 @@ export class MatExpansionPanelHeader implements OnDestroy, FocusableOption { return this.panel.id; } + /** Gets the toggle position for the header. */ + _getTogglePosition(): MatAccordionTogglePosition { + return this.panel.togglePosition; + } + /** Gets whether the expand indicator should be shown. */ _showToggle(): boolean { return !this.panel.hideToggle && !this.panel.disabled; diff --git a/src/material/expansion/expansion-panel.ts b/src/material/expansion/expansion-panel.ts index 8f93c73a16d7..c7ec1d4c305b 100644 --- a/src/material/expansion/expansion-panel.ts +++ b/src/material/expansion/expansion-panel.ts @@ -39,7 +39,7 @@ import {Subject} from 'rxjs'; import {filter, startWith, take, distinctUntilChanged} from 'rxjs/operators'; import {matExpansionAnimations} from './expansion-animations'; import {MatExpansionPanelContent} from './expansion-panel-content'; -import {MAT_ACCORDION, MatAccordionBase} from './accordion-base'; +import {MAT_ACCORDION, MatAccordionBase, MatAccordionTogglePosition} from './accordion-base'; /** MatExpansionPanel's states. */ export type MatExpansionPanelState = 'expanded' | 'collapsed'; @@ -100,8 +100,9 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS = }) export class MatExpansionPanel extends CdkAccordionItem implements AfterContentInit, OnChanges, OnDestroy { - private _document: Document; + private _hideToggle = false; + private _togglePosition: MatAccordionTogglePosition; /** Whether the toggle indicator should be hidden. */ @Input() @@ -111,7 +112,15 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI set hideToggle(value: boolean) { this._hideToggle = coerceBooleanProperty(value); } - private _hideToggle = false; + + /** Whether the toggle indicator should be hidden. */ + @Input() + get togglePosition(): MatAccordionTogglePosition { + return this._togglePosition || (this.accordion && this.accordion.togglePosition); + } + set togglePosition(value: MatAccordionTogglePosition) { + this._togglePosition = value; + } /** An event emitted after the body's expansion animation happens. */ @Output() afterExpand = new EventEmitter(); diff --git a/tools/public_api_guard/material/expansion.d.ts b/tools/public_api_guard/material/expansion.d.ts index 421f70164a6f..b5be0dd5e391 100644 --- a/tools/public_api_guard/material/expansion.d.ts +++ b/tools/public_api_guard/material/expansion.d.ts @@ -8,6 +8,7 @@ export declare class MatAccordion extends CdkAccordion implements MatAccordionBa _headers: QueryList; displayMode: MatAccordionDisplayMode; hideToggle: boolean; + togglePosition: MatAccordionTogglePosition; _handleHeaderFocus(header: MatExpansionPanelHeader): void; _handleHeaderKeydown(event: KeyboardEvent): void; ngAfterContentInit(): void; @@ -18,10 +19,13 @@ export interface MatAccordionBase extends CdkAccordion { _handleHeaderKeydown: (event: KeyboardEvent) => void; displayMode: MatAccordionDisplayMode; hideToggle: boolean; + togglePosition: MatAccordionTogglePosition; } export declare type MatAccordionDisplayMode = 'default' | 'flat'; +export declare type MatAccordionTogglePosition = 'before' | 'after'; + export declare const matExpansionAnimations: { readonly indicatorRotate: AnimationTriggerMetadata; readonly expansionHeaderHeight: AnimationTriggerMetadata; @@ -43,6 +47,7 @@ export declare class MatExpansionPanel extends CdkAccordionItem implements After afterCollapse: EventEmitter; afterExpand: EventEmitter; hideToggle: boolean; + togglePosition: MatAccordionTogglePosition; constructor(accordion: MatAccordionBase, _changeDetectorRef: ChangeDetectorRef, _uniqueSelectionDispatcher: UniqueSelectionDispatcher, _viewContainerRef: ViewContainerRef, _document: any, _animationMode: string, defaultOptions?: MatExpansionPanelDefaultOptions); _containsFocus(): boolean; _getExpandedState(): MatExpansionPanelState; @@ -77,6 +82,7 @@ export declare class MatExpansionPanelHeader implements OnDestroy, FocusableOpti constructor(panel: MatExpansionPanel, _element: ElementRef, _focusMonitor: FocusMonitor, _changeDetectorRef: ChangeDetectorRef, defaultOptions?: MatExpansionPanelDefaultOptions); _getExpandedState(): string; _getPanelId(): string; + _getTogglePosition(): MatAccordionTogglePosition; _isExpanded(): boolean; _keydown(event: KeyboardEvent): void; _showToggle(): boolean;