diff --git a/src/cdk/css/css.ts b/src/cdk/css/css.ts new file mode 100644 index 000000000000..fbbd245df10a --- /dev/null +++ b/src/cdk/css/css.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + + /* Parses arguments into a ngClass-like object */ +export function parseClassList(classList: + undefined|string|string[]|Set|{[klass: string]: any}) { + let klasses = {}; + + if (classList) { + if (Array.isArray(classList) || classList instanceof Set) { + (classList).forEach((klass: string) => klasses[klass] = true); + } else if (typeof classList === 'string') { + klasses = (classList).split(' ').reduce((obj: any, className: string) => { + obj[className] = true; + return obj; + }, {}); + } + } + + return klasses; +} + +/** Parses a ngClass-like object into an array of strings */ +export function getClassList(classList: {[klass: string]: any}): string[] { + return Object.keys(classList).reduce((arr: string[], className: string) => { + if (classList[className]) { + arr.push(className); + } + return arr; + }, []); +} + +/** Parses a ngClass-like object into an array of strings */ +export function parseAndGetClassList( + klasses: undefined|string|string[]|Set|{[klass: string]: any}) { + return getClassList(parseClassList(klasses)); +} diff --git a/src/cdk/css/index.ts b/src/cdk/css/index.ts new file mode 100644 index 000000000000..5be79f041315 --- /dev/null +++ b/src/cdk/css/index.ts @@ -0,0 +1 @@ +export * from './css'; diff --git a/src/cdk/css/public_api.ts b/src/cdk/css/public_api.ts new file mode 100644 index 000000000000..699c5c4b8dc3 --- /dev/null +++ b/src/cdk/css/public_api.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './css'; diff --git a/src/cdk/css/tsconfig-build.json b/src/cdk/css/tsconfig-build.json new file mode 100644 index 000000000000..ff6ed73928df --- /dev/null +++ b/src/cdk/css/tsconfig-build.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig-build", + "files": [ + "public_api.ts" + ], + "angularCompilerOptions": { + "annotateForClosureCompiler": true, + "strictMetadataEmit": true, + "flatModuleOutFile": "index.js", + "flatModuleId": "@angular/cdk/css", + "skipTemplateCodegen": true + } +} diff --git a/src/cdk/overlay/overlay-config.ts b/src/cdk/overlay/overlay-config.ts index cd362ced3fe0..924392441f7a 100644 --- a/src/cdk/overlay/overlay-config.ts +++ b/src/cdk/overlay/overlay-config.ts @@ -21,7 +21,7 @@ export class OverlayConfig { scrollStrategy?: ScrollStrategy = new NoopScrollStrategy(); /** Custom class to add to the overlay pane. */ - panelClass?: string | string[] = ''; + panelClass?: string|string[]|Set|{[klass: string]: any}; /** Whether the overlay has a backdrop. */ hasBackdrop?: boolean = false; diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index d1bd5c4657b9..ce9ddd29b8d4 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -11,6 +11,7 @@ import {PortalHost, Portal} from '@angular/cdk/portal'; import {OverlayConfig} from './overlay-config'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; +import {parseAndGetClassList} from '@angular/cdk/css'; import {first} from 'rxjs/operator/first'; @@ -77,11 +78,7 @@ export class OverlayRef implements PortalHost { if (this._config.panelClass) { // We can't do a spread here, because IE doesn't support setting multiple classes. - if (Array.isArray(this._config.panelClass)) { - this._config.panelClass.forEach(cls => this._pane.classList.add(cls)); - } else { - this._pane.classList.add(this._config.panelClass); - } + parseAndGetClassList(this._config.panelClass).forEach(cls => this._pane.classList.add(cls)); } // Only emit the `attachments` event once all other setup is done. diff --git a/src/lib/datepicker/datepicker-content.html b/src/lib/datepicker/datepicker-content.html index ec6461ce7484..b0fb618c22e2 100644 --- a/src/lib/datepicker/datepicker-content.html +++ b/src/lib/datepicker/datepicker-content.html @@ -1,4 +1,5 @@ implements OnDestroy { */ @Input() touchUi = false; + /** Classes to be passed to the date picker panel. Supports the same syntax as `ngClass`. */ +@Input() panelClass: string|string[]|Set|{[key: string]: any}; + /** Whether the datepicker pop-up should be disabled. */ @Input() get disabled() { diff --git a/src/lib/dialog/dialog-config.ts b/src/lib/dialog/dialog-config.ts index 71ff36ad6621..d7fe5312b42d 100644 --- a/src/lib/dialog/dialog-config.ts +++ b/src/lib/dialog/dialog-config.ts @@ -40,7 +40,7 @@ export class MatDialogConfig { role?: DialogRole = 'dialog'; /** Custom class for the overlay pane. */ - panelClass?: string | string[] = ''; + panelClass?: string|string[]|Set|{[klass: string]: any} = ''; /** Whether the dialog has a backdrop. */ hasBackdrop?: boolean = true; diff --git a/src/lib/menu/menu-directive.ts b/src/lib/menu/menu-directive.ts index b7d2fbd29b65..0c8e3672b537 100644 --- a/src/lib/menu/menu-directive.ts +++ b/src/lib/menu/menu-directive.ts @@ -37,6 +37,7 @@ import {throwMatMenuInvalidPositionX, throwMatMenuInvalidPositionY} from './menu import {MatMenuItem} from './menu-item'; import {MatMenuPanel} from './menu-panel'; import {MenuPositionX, MenuPositionY} from './menu-positions'; +import {parseClassList} from '@angular/cdk/css'; /** Default `mat-menu` options that can be overridden. */ @@ -127,15 +128,20 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnDestroy { * menu template that displays in the overlay container. Otherwise, it's difficult * to style the containing menu from outside the component. * @param classes list of class names + * @deprecated Use `panelClass` instead. */ - @Input('class') - set classList(classes: string) { - if (classes && classes.length) { - this._classList = classes.split(' ').reduce((obj: any, className: string) => { - obj[className] = true; - return obj; - }, {}); + @Input('class') classList = this.panelClass; + /** + * This method takes classes set on the host mat-menu element and applies them on the + * menu template that displays in the overlay container. Otherwise, it's difficult + * to style the containing menu from outside the component. + * @param classes list of class names + */ + @Input() + set panelClass(classList: string|string[]|Set|{[klass: string]: any}) { + if (classList) { + this._classList = parseClassList(classList); this._elementRef.nativeElement.className = ''; this.setPositionClasses(); } diff --git a/src/lib/snack-bar/snack-bar-config.ts b/src/lib/snack-bar/snack-bar-config.ts index 67eabd078b51..20036638e67a 100644 --- a/src/lib/snack-bar/snack-bar-config.ts +++ b/src/lib/snack-bar/snack-bar-config.ts @@ -34,9 +34,15 @@ export class MatSnackBarConfig { /** The length of time in milliseconds to wait before automatically dismissing the snack bar. */ duration?: number = 0; - /** Extra CSS classes to be added to the snack bar container. */ + /** + * Extra CSS classes to be added to the snack bar container. + * @deprecated Use `panelClass` instead. + */ extraClasses?: string[]; + /** Extra CSS classes to be added to the snack bar container. */ + panelClass?: string|string[]|Set|{[klass: string]: any}; + /** Text layout direction for the snack bar. */ direction?: Direction = 'ltr'; diff --git a/src/lib/snack-bar/snack-bar-container.ts b/src/lib/snack-bar/snack-bar-container.ts index 0e355b3887e2..5d24d237a639 100644 --- a/src/lib/snack-bar/snack-bar-container.ts +++ b/src/lib/snack-bar/snack-bar-container.ts @@ -37,6 +37,7 @@ import {AnimationCurves, AnimationDurations} from '@angular/material/core'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; import {MatSnackBarConfig} from './snack-bar-config'; +import {parseAndGetClassList} from '@angular/cdk/css'; export const SHOW_ANIMATION = @@ -104,12 +105,10 @@ export class MatSnackBarContainer extends BasePortalHost implements OnDestroy { throw Error('Attempting to attach snack bar content after content is already attached'); } - if (this.snackBarConfig.extraClasses) { - // Not the most efficient way of adding classes, but the renderer doesn't allow us - // to pass in an array or a space-separated list. - for (let cssClass of this.snackBarConfig.extraClasses) { - this._renderer.addClass(this._elementRef.nativeElement, cssClass); - } + if (this.snackBarConfig.extraClasses || this.snackBarConfig.panelClass) { + const klasses = this.snackBarConfig.extraClasses || this.snackBarConfig.panelClass; + parseAndGetClassList(klasses) + .forEach(klass => this._renderer.addClass(this._elementRef.nativeElement, klass)); } if (this.snackBarConfig.horizontalPosition === 'center') {