diff --git a/src/lib/api/core/base-adapter.ts b/src/lib/api/core/base-adapter.ts index 3735cac74..9d51a5da5 100644 --- a/src/lib/api/core/base-adapter.ts +++ b/src/lib/api/core/base-adapter.ts @@ -73,7 +73,9 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective { } else if (typeof source === 'string') { this._cacheInputString(key, source); } else { - throw new Error('Invalid class value provided. Did you want to cache the raw value?'); + throw new Error( + `Invalid class value '${key}' provided. Did you want to cache the raw value?` + ); } } @@ -124,4 +126,14 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective { protected _cacheInputString(key = '', source?: string) { this._inputMap[key] = source; } + + /** + * Does this directive have 1 or more responsive keys defined + * Note: we exclude the 'baseKey' key (which is NOT considered responsive) + */ + public get usesResponsiveAPI() { + const totalKeys = Object.keys(this._inputMap).length; + const baseValue = this._inputMap[this._baseKey]; + return (totalKeys - (!!baseValue ? 1 : 0)) > 0; + } } diff --git a/src/lib/api/ext/class.spec.ts b/src/lib/api/ext/class.spec.ts index 6ca63aca0..64e2583d8 100644 --- a/src/lib/api/ext/class.spec.ts +++ b/src/lib/api/ext/class.spec.ts @@ -65,21 +65,44 @@ describe('class directive', () => { }); it('should override base `class` values with responsive values', () => { - createTestComponent(`
`); + createTestComponent(`
`); expectNativeEl(fixture).toHaveCssClass('class0'); - expectNativeEl(fixture).not.toHaveCssClass('class1'); + expectNativeEl(fixture).not.toHaveCssClass('what'); expectNativeEl(fixture).not.toHaveCssClass('class2'); + // the CSS classes listed in the string (space delimited) are added, + matchMedia.activate('xs'); + expectNativeEl(fixture).toHaveCssClass('class0'); + expectNativeEl(fixture).toHaveCssClass('what'); + expectNativeEl(fixture).toHaveCssClass('class2'); + + matchMedia.activate('lg'); + expectNativeEl(fixture).toHaveCssClass('class0'); + expectNativeEl(fixture).not.toHaveCssClass('what'); + expectNativeEl(fixture).not.toHaveCssClass('class2'); + }); + + it('should override base `class` values with responsive values', () => { + createTestComponent(` +
+ `); + + expectNativeEl(fixture).toHaveCssClass('class0'); + expectNativeEl(fixture).not.toHaveCssClass('what'); + expectNativeEl(fixture).not.toHaveCssClass('class2'); + + // Object keys are CSS classes that get added when the expression given in + // the value evaluates to a truthy value, otherwise they are removed. matchMedia.activate('xs'); expectNativeEl(fixture).not.toHaveCssClass('class0'); - expectNativeEl(fixture).toHaveCssClass('class1'); + expectNativeEl(fixture).toHaveCssClass('what'); expectNativeEl(fixture).toHaveCssClass('class2'); - // matchMedia.activate('lg'); - // expectNativeEl(fixture).toHaveCssClass('class0'); - // expectNativeEl(fixture).not.toHaveCssClass('class1'); - // expectNativeEl(fixture).not.toHaveCssClass('class2'); + matchMedia.activate('lg'); + expectNativeEl(fixture).toHaveCssClass('class0'); + expectNativeEl(fixture).not.toHaveCssClass('what'); + expectNativeEl(fixture).not.toHaveCssClass('class2'); }); it('should keep the raw existing `class` with responsive updates', () => { diff --git a/src/lib/api/ext/class.ts b/src/lib/api/ext/class.ts index c38f71d3d..d492f8fe9 100644 --- a/src/lib/api/ext/class.ts +++ b/src/lib/api/ext/class.ts @@ -35,7 +35,7 @@ export type NgClassType = string | string[] | Set | {[klass: string]: an */ @Directive({ selector: ` - [class], [class.xs], [class.sm], [class.md], [class.lg], [class.xl], + [class.xs], [class.sm], [class.md], [class.lg], [class.xl], [class.lt-sm], [class.lt-md], [class.lt-lg], [class.lt-xl], [class.gt-xs], [class.gt-sm], [class.gt-md], [class.gt-lg], @@ -77,41 +77,21 @@ export class ClassDirective extends BaseFxDirective /** Deprecated selectors */ - /** - * Base class selector values get applied immediately and are considered destructive overwrites to - * all previous class assignments - * - * Delegate to NgClass:klass setter and cache value for base fallback from responsive APIs. - */ - @Input('class') - set classBase(val: string) { - this._classAdapter.cacheInput('_rawClass', val, true); - this._ngClassInstance.klass = val; - } + @Input('class.xs') set classXs(val: NgClassType) { this._classAdapter.cacheInput('classXs', val); } + @Input('class.sm') set classSm(val: NgClassType) { this._classAdapter.cacheInput('classSm', val); } + @Input('class.md') set classMd(val: NgClassType) { this._classAdapter.cacheInput('classMd', val); } + @Input('class.lg') set classLg(val: NgClassType) { this._classAdapter.cacheInput('classLg', val); } + @Input('class.xl') set classXl(val: NgClassType) { this._classAdapter.cacheInput('classXl', val); } - @Input('class.xs') set classXs(val: NgClassType) { this._classAdapter.cacheInput('classXs', val, true); } - @Input('class.sm') set classSm(val: NgClassType) { this._classAdapter.cacheInput('classSm', val, true); } - @Input('class.md') set classMd(val: NgClassType) { this._classAdapter.cacheInput('classMd', val, true); } - @Input('class.lg') set classLg(val: NgClassType) { this._classAdapter.cacheInput('classLg', val, true); } - @Input('class.xl') set classXl(val: NgClassType) { this._classAdapter.cacheInput('classXl', val, true); } + @Input('class.lt-sm') set classLtSm(val: NgClassType) { this._classAdapter.cacheInput('classLtSm', val); } + @Input('class.lt-md') set classLtMd(val: NgClassType) { this._classAdapter.cacheInput('classLtMd', val); } + @Input('class.lt-lg') set classLtLg(val: NgClassType) { this._classAdapter.cacheInput('classLtLg', val); } + @Input('class.lt-xl') set classLtXl(val: NgClassType) { this._classAdapter.cacheInput('classLtXl', val); } - @Input('class.lt-sm') set classLtSm(val: NgClassType) { this._classAdapter.cacheInput('classLtSm', val, true); } - @Input('class.lt-md') set classLtMd(val: NgClassType) { this._classAdapter.cacheInput('classLtMd', val, true); } - @Input('class.lt-lg') set classLtLg(val: NgClassType) { this._classAdapter.cacheInput('classLtLg', val, true); } - @Input('class.lt-xl') set classLtXl(val: NgClassType) { this._classAdapter.cacheInput('classLtXl', val, true); } - - @Input('class.gt-xs') set classGtXs(val: NgClassType) { this._classAdapter.cacheInput('classGtXs', val, true); } - @Input('class.gt-sm') set classGtSm(val: NgClassType) { this._classAdapter.cacheInput('classGtSm', val, true); } - @Input('class.gt-md') set classGtMd(val: NgClassType) { this._classAdapter.cacheInput('classGtMd', val, true); } - @Input('class.gt-lg') set classGtLg(val: NgClassType) { this._classAdapter.cacheInput('classGtLg', val, true); } - - /** - * Initial value of the `class` attribute; used as - * fallback and will be merged with nay `ngClass` values - */ - get initialClasses() : string { - return this._classAdapter.queryInput('_rawClass') || ""; - } + @Input('class.gt-xs') set classGtXs(val: NgClassType) { this._classAdapter.cacheInput('classGtXs', val); } + @Input('class.gt-sm') set classGtSm(val: NgClassType) { this._classAdapter.cacheInput('classGtSm', val); } + @Input('class.gt-md') set classGtMd(val: NgClassType) { this._classAdapter.cacheInput('classGtMd', val); } + @Input('class.gt-lg') set classGtLg(val: NgClassType) { this._classAdapter.cacheInput('classGtLg', val); } /* tslint:enable */ constructor(protected monitor: MediaMonitor, @@ -121,8 +101,9 @@ export class ClassDirective extends BaseFxDirective super(monitor, _ngEl, _renderer); - this._classAdapter = new BaseFxDirectiveAdapter('class', monitor, _ngEl, _renderer); this._ngClassAdapter = new BaseFxDirectiveAdapter('ngClass', monitor, _ngEl, _renderer); + this._classAdapter = new BaseFxDirectiveAdapter('class', monitor, _ngEl, _renderer); + this._classAdapter.cacheInput('class', _ngEl.nativeElement.getAttribute('class') || ''); // Create an instance NgClass Directive instance only if `ngClass=""` has NOT been defined on // the same host element; since the responsive variations may be defined... @@ -157,7 +138,9 @@ export class ClassDirective extends BaseFxDirective if (!this._classAdapter.hasMediaQueryListener) { this._configureMQListener(); } - this._ngClassInstance.ngDoCheck(); + if ( this._ngClassInstance) { + this._ngClassInstance.ngDoCheck(); + } } ngOnDestroy() { @@ -175,11 +158,12 @@ export class ClassDirective extends BaseFxDirective * mql change events to onMediaQueryChange handlers */ protected _configureMQListener() { - this._classAdapter.listenForMediaQueryChanges('class', '', (changes: MediaChange) => { + const value = this._classAdapter.queryInput('class'); + this._classAdapter.listenForMediaQueryChanges('class', value, (changes: MediaChange) => { this._updateKlass(changes.value); }); - this._ngClassAdapter.listenForMediaQueryChanges('ngClass', '', (changes: MediaChange) => { + this._ngClassAdapter.listenForMediaQueryChanges('ngClass', value, (changes: MediaChange) => { this._updateNgClass(changes.value); this._ngClassInstance.ngDoCheck(); // trigger NgClass::_applyIterableChanges() }); @@ -190,11 +174,11 @@ export class ClassDirective extends BaseFxDirective * ::ngDoCheck() is not needed */ protected _updateKlass(value?: NgClassType) { - let klass = value || this._classAdapter.queryInput('class') || ''; + let klass = value || this._classAdapter.queryInput('class'); if (this._classAdapter.mqActivation) { klass = this._classAdapter.mqActivation.activatedInput; } - this._ngClassInstance.klass = klass || this.initialClasses; + this._ngClassInstance.klass = klass; } /**