diff --git a/src/lib/flexbox/api/base-adapter.spec.ts b/src/lib/api/core/base-adapter.spec.ts similarity index 100% rename from src/lib/flexbox/api/base-adapter.spec.ts rename to src/lib/api/core/base-adapter.spec.ts diff --git a/src/lib/flexbox/api/base-adapter.ts b/src/lib/api/core/base-adapter.ts similarity index 97% rename from src/lib/flexbox/api/base-adapter.ts rename to src/lib/api/core/base-adapter.ts index ee48afc8b..3735cac74 100644 --- a/src/lib/flexbox/api/base-adapter.ts +++ b/src/lib/api/core/base-adapter.ts @@ -8,7 +8,7 @@ import {ElementRef, Renderer2} from '@angular/core'; import {BaseFxDirective} from './base'; -import {ResponsiveActivation} from './../responsive/responsive-activation'; +import {ResponsiveActivation} from './responsive-activation'; import {MediaQuerySubscriber} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; diff --git a/src/lib/flexbox/api/base.ts b/src/lib/api/core/base.ts similarity index 98% rename from src/lib/flexbox/api/base.ts rename to src/lib/api/core/base.ts index ecfcfbdce..487656d4b 100644 --- a/src/lib/flexbox/api/base.ts +++ b/src/lib/api/core/base.ts @@ -19,7 +19,7 @@ import { applyStyleToElements } from '../../utils/style-utils'; -import {ResponsiveActivation, KeyOptions} from '../responsive/responsive-activation'; +import {ResponsiveActivation, KeyOptions} from '../core/responsive-activation'; import {MediaMonitor} from '../../media-query/media-monitor'; import {MediaQuerySubscriber} from '../../media-query/media-change'; diff --git a/src/lib/flexbox/responsive/responsive-activation.spec.ts b/src/lib/api/core/responsive-activation.spec.ts similarity index 100% rename from src/lib/flexbox/responsive/responsive-activation.spec.ts rename to src/lib/api/core/responsive-activation.spec.ts diff --git a/src/lib/flexbox/responsive/responsive-activation.ts b/src/lib/api/core/responsive-activation.ts similarity index 99% rename from src/lib/flexbox/responsive/responsive-activation.ts rename to src/lib/api/core/responsive-activation.ts index d27e31864..5eeb31de4 100644 --- a/src/lib/flexbox/responsive/responsive-activation.ts +++ b/src/lib/api/core/responsive-activation.ts @@ -61,7 +61,7 @@ export class ResponsiveActivation { * important when several media queries are 'registered' and from which, the browser uses the * first matching media query. */ - get registryFromLargest():BreakPointX[] { + get registryFromLargest(): BreakPointX[] { return [...this._registryMap].reverse(); } diff --git a/src/lib/flexbox/api/class.spec.ts b/src/lib/api/ext/class.spec.ts similarity index 100% rename from src/lib/flexbox/api/class.spec.ts rename to src/lib/api/ext/class.spec.ts diff --git a/src/lib/flexbox/api/class.ts b/src/lib/api/ext/class.ts similarity index 98% rename from src/lib/flexbox/api/class.ts rename to src/lib/api/ext/class.ts index 578bcda15..3c2efa928 100644 --- a/src/lib/flexbox/api/class.ts +++ b/src/lib/api/ext/class.ts @@ -22,8 +22,8 @@ import { } from '@angular/core'; import {NgClass} from '@angular/common'; -import {BaseFxDirective} from './base'; -import {BaseFxDirectiveAdapter} from './base-adapter'; +import {BaseFxDirective} from '../core/base'; +import {BaseFxDirectiveAdapter} from '../core/base-adapter'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; diff --git a/src/lib/flexbox/api/hide.spec.ts b/src/lib/api/ext/hide.spec.ts similarity index 100% rename from src/lib/flexbox/api/hide.spec.ts rename to src/lib/api/ext/hide.spec.ts diff --git a/src/lib/api/ext/img-src.spec.ts b/src/lib/api/ext/img-src.spec.ts new file mode 100644 index 000000000..7abb6d4d9 --- /dev/null +++ b/src/lib/api/ext/img-src.spec.ts @@ -0,0 +1,191 @@ +/** + * @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 + */ +import {Component} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {ComponentFixture, TestBed, inject} from '@angular/core/testing'; + +import {DEFAULT_BREAKPOINTS_PROVIDER} from '../../media-query/breakpoints/break-points-provider'; +import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry'; +import {MockMatchMedia} from '../../media-query/mock/mock-match-media'; +import {MatchMedia} from '../../media-query/match-media'; +import {FlexLayoutModule} from '../../module'; + +import {customMatchers} from '../../utils/testing/custom-matchers'; +import {makeCreateTestComponent, queryFor} from '../../utils/testing/helpers'; +import {expect} from '../../utils/testing/custom-matchers'; + +const SRC_URLS = { + 'xs': [ + 'https://dummyimage.com/300x200/c7751e/fff.png', + 'https://dummyimage.com/300x200/c7751e/000.png' + ], + 'gt-xs': [ + 'https://dummyimage.com/400x250/c7c224/fff.png', + 'https://dummyimage.com/400x250/c7c224/000.png' + ], + 'md': [ + 'https://dummyimage.com/500x300/76c720/fff.png', + 'https://dummyimage.com/500x300/76c720/000.png' + ], + 'lt-lg': [ + 'https://dummyimage.com/600x350/25c794/fff.png', + 'https://dummyimage.com/600x350/25c794/000.png' + ], + 'lg': [ + 'https://dummyimage.com/700x400/258cc7/fff.png', + 'https://dummyimage.com/700x400/258cc7/000.png' + ], + 'lt-xl': [ + 'https://dummyimage.com/800x500/b925c7/ffffff.png', + 'https://dummyimage.com/800x500/b925c7/000.png' + ] +}; +const DEFAULT_SRC = 'https://dummyimage.com/300x300/c72538/ffffff.png'; + +describe('img-src directive', () => { + let fixture: ComponentFixture; + let matchMedia: MockMatchMedia; + let breakpoints: BreakPointRegistry; + + let componentWithTemplate = (template: string) => { + fixture = makeCreateTestComponent(() => TestSrcsetComponent)(template); + + inject([MatchMedia, BreakPointRegistry], + (_matchMedia: MockMatchMedia, _breakpoints: BreakPointRegistry) => { + matchMedia = _matchMedia; + breakpoints = _breakpoints; + })(); + }; + + beforeEach(() => { + jasmine.addMatchers(customMatchers); + + // Configure testbed to prepare services + TestBed.configureTestingModule({ + imports: [CommonModule, FlexLayoutModule], + declarations: [TestSrcsetComponent], + providers: [ + BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, + {provide: MatchMedia, useClass: MockMatchMedia} + ] + }); + }); + + describe('with static api', () => { + it('should preserve the static src attribute', () => { + componentWithTemplate(` + + `); + const img = queryFor(fixture, 'img')[0].nativeElement; + + fixture.detectChanges(); + expect(img).toHaveAttributes({ + src: 'https://dummyimage.com/300x300/c72538/ffffff.png' + }); + }); + + it('should work standard input bindings', () => { + componentWithTemplate(` + + `); + const img = queryFor(fixture, 'img')[0].nativeElement; + + fixture.detectChanges(); + expect(img).toHaveAttributes({ + src: 'https://dummyimage.com/300x300/c72538/ffffff.png' + }); + }); + + it('should work when no `src` value is defined', () => { + componentWithTemplate(` + + `); + + const img = queryFor(fixture, 'img')[0].nativeElement; + fixture.detectChanges(); + expect(img).toHaveAttributes({ + src: '' + }); + }); + }); + + describe('with responsive api', () => { + + it('should work with a isolated image element and responsive srcs', () => { + componentWithTemplate(` + + `); + fixture.detectChanges(); + + let img = queryFor(fixture, 'img')[0].nativeElement; + + matchMedia.activate('md'); + fixture.detectChanges(); + expect(img).toBeDefined(); + expect(img).toHaveAttributes({ + src: SRC_URLS['md'][0] + }); + + // When activating an unused breakpoint, fallback to default [src] value + matchMedia.activate('xl'); + fixture.detectChanges(); + expect(img).toHaveAttributes({ + src: SRC_URLS['xs'][0] + }); + }); + + it('should work use [src] if default [src] is not defined', () => { + componentWithTemplate(` + + `); + fixture.detectChanges(); + matchMedia.activate('md'); + fixture.detectChanges(); + + let img = queryFor(fixture, 'img')[0].nativeElement; + expect(img).toBeDefined(); + expect(img).toHaveAttributes({ + src: SRC_URLS['md'][0] + }); + + // When activating an unused breakpoint, fallback to default [src] value + matchMedia.activate('xl'); + fixture.detectChanges(); + expect(img).toHaveAttributes({ + src: '' + }); + }); + + }); +}); + +// ***************************************************************** +// Template Component +// ***************************************************************** + +@Component({ + selector: 'test-src-api', + template: '' +}) +export class TestSrcsetComponent { + defaultSrc = ''; + xsSrc = ''; + mdSrc = ''; + lgSrc = ''; + + constructor() { + this.defaultSrc = DEFAULT_SRC; + this.xsSrc = SRC_URLS['xs'][0]; + this.mdSrc = SRC_URLS['md'][0]; + this.lgSrc = SRC_URLS['lg'][0]; + + } +} + + diff --git a/src/lib/api/ext/img-src.ts b/src/lib/api/ext/img-src.ts new file mode 100644 index 000000000..ae1043022 --- /dev/null +++ b/src/lib/api/ext/img-src.ts @@ -0,0 +1,123 @@ +/** + * @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 + */ +import { + Directive, + ElementRef, + Input, + OnInit, + OnChanges, + Renderer2 +} from '@angular/core'; +import {ɵgetDOM as getDom} from '@angular/platform-browser'; + +import {BaseFxDirective} from '../core/base'; +import {MediaMonitor} from '../../media-query/media-monitor'; + +/** + * This directive provides a responsive API for the HTML 'src' attribute + * and will update the img.src property upon each responsive activation. + * Note: This solution is complementary to using the `img.srcset` approaches. Both are + * published to support developer preference. + * + * e.g. + * + * + * @see https://css-tricks.com/responsive-images-youre-just-changing-resolutions-use-src/ + */ +@Directive({ + selector: ` + [src], + [src.xs], [src.sm], [src.md], [src.lg], [src.xl], + [src.lt-sm], [src.lt-md], [src.lt-lg], [src.lt-xl], + [src.gt-xs], [src.gt-sm], [src.gt-md], [src.gt-lg] +` +}) +export class ImgSrcDirective extends BaseFxDirective implements OnInit, OnChanges { + + /* tslint:disable */ + @Input('src') set srcBase(val) { this.cacheDefaultSrc(val); } + + @Input('src.xs') set srcXs(val) { this._cacheInput('srcXs', val); } + @Input('src.sm') set srcSm(val) { this._cacheInput('srcSm', val); } + @Input('src.md') set srcMd(val) { this._cacheInput('srcMd', val); } + @Input('src.lg') set srcLg(val) { this._cacheInput('srcLg', val); } + @Input('src.xl') set srcXl(val) { this._cacheInput('srcXl', val); } + + @Input('src.lt-sm') set srcLtSm(val) { this._cacheInput('srcLtSm', val); } + @Input('src.lt-md') set srcLtMd(val) { this._cacheInput('srcLtMd', val); } + @Input('src.lt-lg') set srcLtLg(val) { this._cacheInput('srcLtLg', val); } + @Input('src.lt-xl') set srcLtXl(val) { this._cacheInput('srcLtXl', val); } + + @Input('src.gt-xs') set srcGtXs(val) { this._cacheInput('srcGtXs', val); } + @Input('src.gt-sm') set srcGtSm(val) { this._cacheInput('srcGtSm', val); } + @Input('src.gt-md') set srcGtMd(val) { this._cacheInput('srcGtMd', val); } + @Input('src.gt-lg') set srcGtLg(val) { this._cacheInput('srcGtLg', val); } + /* tslint:enable */ + + constructor(elRef: ElementRef, renderer: Renderer2, monitor: MediaMonitor) { + super(monitor, elRef, renderer); + } + + /** + * Listen for responsive changes to update the img.src attribute + */ + ngOnInit() { + super.ngOnInit(); + + // Cache initial value of `src` to use as responsive fallback + this.cacheDefaultSrc(this.defaultSrc); + + // Listen for responsive changes + this._listenForMediaQueryChanges('src', this.defaultSrc, () => { + this._updateSrcFor(); + }); + this._updateSrcFor(); + } + + /** + * Update the 'src' property of the host element + */ + ngOnChanges() { + if (this.hasInitialized) { + this._updateSrcFor(); + } + } + + /** + * Use the [responsively] activated input value to update + * the host img src attribute. + */ + protected _updateSrcFor() { + if (this._mqActivation) { + let url = this._mqActivation.activatedInput || ''; + this._renderer.setAttribute(this.nativeElement, 'src', url); + } + } + + + /** + * Cache initial value of 'src', this will be used as fallback when breakpoint + * activations change. + * NOTE: The default 'src' property is not bound using @Input(), so perform + * a post-ngOnInit() lookup of the default src value (if any). + */ + protected cacheDefaultSrc(value?: string) { + const currentVal = this._queryInput('src'); + if (typeof currentVal == 'undefined') { + this._cacheInput('src', value || ''); + } + } + + /** + * Empty values are maintained, undefined values are exposed as '' + */ + protected get defaultSrc(): string { + let attrVal = getDom().getAttribute(this.nativeElement, 'src'); + return this._queryInput('src') || attrVal || ''; + } +} diff --git a/src/lib/flexbox/api/img-srcset.spec.ts b/src/lib/api/ext/img-srcset.spec.ts similarity index 77% rename from src/lib/flexbox/api/img-srcset.spec.ts rename to src/lib/api/ext/img-srcset.spec.ts index c400e6b2d..c02d5811b 100644 --- a/src/lib/flexbox/api/img-srcset.spec.ts +++ b/src/lib/api/ext/img-srcset.spec.ts @@ -48,7 +48,7 @@ const SRCSET_URLS_MAP = { }; const DEFAULT_SRC = 'https://dummyimage.com/300x300/c72538/ffffff.png'; -fdescribe('srcset directive', () => { +describe('img-srcset directive', () => { let fixture: ComponentFixture; let matchMedia: MockMatchMedia; let breakpoints: BreakPointRegistry; @@ -79,8 +79,8 @@ fdescribe('srcset directive', () => { it('should work without a wrapper element', () => { const template = ` - - `; + + `; componentWithTemplate(template); fixture.detectChanges(); @@ -91,10 +91,24 @@ fdescribe('srcset directive', () => { expect(pictures.length).toBe(0); }); - it('should work with a bindable img.srcset', () => { + it('should preserve the static img.srcset attribute', () => { const template = ` - - `; + + `; + componentWithTemplate(template); + fixture.detectChanges(); + + const img = queryFor(fixture, 'img')[0].nativeElement; + expect(img).toHaveAttributes({ + srcset: 'https://dummyimage.com/300x300/c72538/ffffff.png' + }); + + }); + + it('should preserve bindable img.srcset attribute', () => { + const template = ` + + `; componentWithTemplate(template); fixture.detectChanges(); @@ -106,13 +120,12 @@ fdescribe('srcset directive', () => { expect(pictures.length).toBe(0); expect(img).toBeDefined(); expect(img).toHaveAttributes({ - src : 'https://dummyimage.com/300x300/c72538/ffffff.png', - srcset : fixture.componentInstance.lgSrcSet - }) - + src: 'https://dummyimage.com/300x300/c72538/ffffff.png', + srcset: fixture.componentInstance.lgSrcSet + }); }); - it('should work when no srcset flex-layout directive is used', () => { + it('should work when no "srcset" directive is used', () => { const template = ` @@ -129,7 +142,7 @@ fdescribe('srcset directive', () => { expect(_.tagName(_.lastElementChild(pictureElt))).toEqual('IMG'); }); - it('should keep img as the last child tag of after source tags injection', () => { + it('should keep img as the last child tag of ', () => { const template = `
@@ -240,6 +253,57 @@ fdescribe('srcset directive', () => { media: breakpoints.findByAlias('xs').mediaQuery }); }); + + describe('with responsive api', () => { + + it('should work with a isolated image element and responsive srcset(s)', () => { + componentWithTemplate(` + + `); + + let img = queryFor(fixture, 'img')[0].nativeElement; + + matchMedia.activate('md'); + fixture.detectChanges(); + expect(img).toBeDefined(); + expect(img).toHaveAttributes({ + src: fixture.componentInstance.mdSrcSet + }); + + // When activating an unused breakpoint, fallback to default [srcset] value + matchMedia.activate('xl'); + fixture.detectChanges(); + expect(img).toHaveAttributes({ + src: fixture.componentInstance.xsSrcSet + }); + }); + + it('should work use [src] if default [srcset] is not defined', () => { + componentWithTemplate(` + + `); + fixture.detectChanges(); + let img = queryFor(fixture, 'img')[0].nativeElement; + + matchMedia.activate('md'); + fixture.detectChanges(); + + expect(img).toBeDefined(); + expect(img).toHaveAttributes({ + src: fixture.componentInstance.mdSrcSet + }); + + // When activating an unused breakpoint, fallback to default [srcset] value + matchMedia.activate('xl'); + fixture.detectChanges(); + expect(img).toHaveAttributes({ + src: DEFAULT_SRC + }); + }); + + }); }); // ***************************************************************** diff --git a/src/lib/flexbox/api/img-srcset.ts b/src/lib/api/ext/img-srcset.ts similarity index 71% rename from src/lib/flexbox/api/img-srcset.ts rename to src/lib/api/ext/img-srcset.ts index afaafc1b4..656486787 100644 --- a/src/lib/flexbox/api/img-srcset.ts +++ b/src/lib/api/ext/img-srcset.ts @@ -7,19 +7,20 @@ */ import { Directive, + ElementRef, Input, OnInit, OnChanges, - ElementRef, + OnDestroy, Renderer2, SimpleChanges } from '@angular/core'; import {ɵgetDOM as getDom} from '@angular/platform-browser'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {MediaMonitor} from '../../media-query/media-monitor'; import {MediaChange} from '../../media-query/media-change'; -import {BreakPointX} from '../responsive/responsive-activation'; +import {BreakPointX} from '../core/responsive-activation'; const DEFAULT_SRCSET = 'srcset'; @@ -29,9 +30,18 @@ const DEFAULT_SRCSET = 'srcset'; * 1) standalone , or * 2) nested . * - * The latter usage will inject [into the parent element] elements - * with media and srcset attributes. Note that elements are sorted according - * to the related media query : from largest to smallest + * In both cases the expression/value assigned is simply the appropriate image url. + * e.g. + * + * + * + * + * + * For standalone (1) usages, this directive will update the img.src property with the responsive + * activated input value. For picture (2) usages, this directive will inject + * [into the parent element] elements with media and srcset attributes. + * Note that elements are sorted according to the related media query : + * from largest to smallest * * > For browsers not supporting the element, the Picturefill polyfill is still needed. * @@ -43,39 +53,38 @@ const DEFAULT_SRCSET = 'srcset'; */ @Directive({ selector: ` - [srcset], - [srcset.xs], [srcset.sm], [srcset.md], [srcset.lg], [srcset.xl], - [srcset.lt-sm], [srcset.lt-md], [srcset.lt-lg], [srcset.lt-xl], - [srcset.gt-xs], [srcset.gt-sm], [srcset.gt-md], [srcset.gt-lg] + img[srcset], + img[srcset.xs], img[srcset.sm], img[srcset.md], img[srcset.lg], img[srcset.xl], + img[srcset.lt-sm], img[srcset.lt-md], img[srcset.lt-lg], img[srcset.lt-xl], + img[srcset.gt-xs], img[srcset.gt-sm], img[srcset.gt-md], img[srcset.gt-lg] ` }) -export class ImgSrcsetDirective extends BaseFxDirective implements OnInit, OnChanges { +export class ImgSrcsetDirective extends BaseFxDirective implements OnInit, OnChanges, OnDestroy { + /* tslint:disable */ /** * Intercept srcset assignment so we cache the default static value. * When the responsive breakpoint deactivates,it is possible that fallback static * value (which is used to clear the deactivated value) will be used * (if no other breakpoints activate) */ - @Input('srcset') - set srcsetBase(val) { this._cacheInput('srcset', val); } + @Input('srcset') set srcsetBase(val) { this._cacheInput('srcset', val); } - /* tslint:disable */ @Input('srcset.xs') set srcsetXs(val) { this._cacheInput('srcsetXs', val); } @Input('srcset.sm') set srcsetSm(val) { this._cacheInput('srcsetSm', val); } @Input('srcset.md') set srcsetMd(val) { this._cacheInput('srcsetMd', val); } - @Input('srcset.lg') set srcsetLg(val) { this._cacheInput('srcsetLg', val); }; - @Input('srcset.xl') set srcsetXl(val) { this._cacheInput('srcsetXl', val); }; - - @Input('srcset.lt-sm') set srcsetLtSm(val) { this._cacheInput('srcsetLtSm', val); }; - @Input('srcset.lt-md') set srcsetLtMd(val) { this._cacheInput('srcsetLtMd', val); }; - @Input('srcset.lt-lg') set srcsetLtLg(val) { this._cacheInput('srcsetLtLg', val); }; - @Input('srcset.lt-xl') set srcsetLtXl(val) { this._cacheInput('srcsetLtXl', val); }; - - @Input('srcset.gt-xs') set srcsetGtXs(val) { this._cacheInput('srcsetGtXs', val); }; - @Input('srcset.gt-sm') set srcsetGtSm(val) { this._cacheInput('srcsetGtSm', val); }; - @Input('srcset.gt-md') set srcsetGtMd(val) { this._cacheInput('srcsetGtMd', val); }; - @Input('srcset.gt-lg') set srcsetGtLg(val) { this._cacheInput('srcsetGtLg', val); }; + @Input('srcset.lg') set srcsetLg(val) { this._cacheInput('srcsetLg', val); } + @Input('srcset.xl') set srcsetXl(val) { this._cacheInput('srcsetXl', val); } + + @Input('srcset.lt-sm') set srcsetLtSm(val) { this._cacheInput('srcsetLtSm', val); } + @Input('srcset.lt-md') set srcsetLtMd(val) { this._cacheInput('srcsetLtMd', val); } + @Input('srcset.lt-lg') set srcsetLtLg(val) { this._cacheInput('srcsetLtLg', val); } + @Input('srcset.lt-xl') set srcsetLtXl(val) { this._cacheInput('srcsetLtXl', val); } + + @Input('srcset.gt-xs') set srcsetGtXs(val) { this._cacheInput('srcsetGtXs', val); } + @Input('srcset.gt-sm') set srcsetGtSm(val) { this._cacheInput('srcsetGtSm', val); } + @Input('srcset.gt-md') set srcsetGtMd(val) { this._cacheInput('srcsetGtMd', val); } + @Input('srcset.gt-lg') set srcsetGtLg(val) { this._cacheInput('srcsetGtLg', val); } /* tslint:enable */ constructor(elRef: ElementRef, renderer: Renderer2, monitor: MediaMonitor) { @@ -87,13 +96,16 @@ export class ImgSrcsetDirective extends BaseFxDirective implements OnInit, OnCha * activation of the standalone- Img element. */ ngOnInit() { + super.ngOnInit(); + this._configureIsolatedImg(); + // Only responsively update srcset values for stand-alone image elements + // Fallback to [srcset] value for responsive deactivation this._listenForMediaQueryChanges(DEFAULT_SRCSET, '', (changes: MediaChange) => { - let activatedKey = DEFAULT_SRCSET + changes.suffix - this._updateSrcset(activatedKey) + let activatedKey = changes.matches ? DEFAULT_SRCSET + changes.suffix : DEFAULT_SRCSET; + this._updateSrcsetFor(activatedKey); }); - this._configureIsolatedImg(); this._injectSourceElements(); } @@ -102,11 +114,11 @@ export class ImgSrcsetDirective extends BaseFxDirective implements OnInit, OnCha * properties. elements are injected once through ngOnInit */ ngOnChanges(changes: SimpleChanges) { - Object.keys(changes).forEach(key => { - if (!changes[key].firstChange) { - this._updateSrcset(key); - } - }); + if (this.hasInitialized) { + Object.keys(changes).forEach(key => { + this._updateSrcsetFor(key); + }); + } } ngOnDestroy() { @@ -117,19 +129,21 @@ export class ImgSrcsetDirective extends BaseFxDirective implements OnInit, OnCha /** * Responsive activation is used ONLY for standalone images + * * Image tags nested in Picture containers, however, ignore responsive activations * as injected are used. Changes to srcset values for nested images * directly update the injected source elements. + * + * For stand-alone img elements with responsive srcset attributes, the + * img.src property will be responsively updated. */ - protected _updateSrcset(activatedKey: string) { + protected _updateSrcsetFor(activatedKey: string) { if (!this.hasPictureParent) { - let target = this._injectedSourceElements[activatedKey]; - // If databinding is used, then the attribute is removed and - // `ng-reflect-srcset-base` is used; so let's manually restore the attribute. - this._renderer.setAttribute(target, DEFAULT_SRCSET, this._queryInput(activatedKey)); + let target = this.nativeElement; + this._renderer.setAttribute(target, 'src', this._queryInput(activatedKey)); } else { // Identify the correct source and simply update the attribute with the new value - let target = this._injectedSourceElements[activatedKey] || this.nativeElement; + let target = this._injectedSourceElements[activatedKey]; this._renderer.setAttribute(target, DEFAULT_SRCSET, this._queryInput(activatedKey)); } } @@ -167,9 +181,19 @@ export class ImgSrcsetDirective extends BaseFxDirective implements OnInit, OnCha if (!this.hasPictureParent) { let target = this.nativeElement; + // If [srcset] is not defined AND responsive API is in use + // then set [srcset] from the [src] property + + let numInputsUsed = Object.keys(this._inputMap).length; + if ( !this._queryInput(DEFAULT_SRCSET) && numInputsUsed ) { + this.srcsetBase = getDom().getAttribute(target, 'src'); + } + // If databinding is used, then the attribute is removed and // `ng-reflect-srcset-base` is used; so let's manually restore the attribute. + this._renderer.setAttribute(target, DEFAULT_SRCSET, this._queryInput(DEFAULT_SRCSET)); + this._renderer.setProperty(target, DEFAULT_SRCSET, this._queryInput(DEFAULT_SRCSET)); } } @@ -180,7 +204,7 @@ export class ImgSrcsetDirective extends BaseFxDirective implements OnInit, OnCha * */ protected get hasPictureParent() { - return this.parentElement.nodeName == 'PICTURE' + return this.parentElement.nodeName == 'PICTURE'; } /** Reference to injected source elements to be used when there is a need to update their diff --git a/src/lib/flexbox/api/show-hide.ts b/src/lib/api/ext/show-hide.ts similarity index 98% rename from src/lib/flexbox/api/show-hide.ts rename to src/lib/api/ext/show-hide.ts index ec4d8372e..4363e27df 100644 --- a/src/lib/flexbox/api/show-hide.ts +++ b/src/lib/api/ext/show-hide.ts @@ -20,10 +20,10 @@ import { import {Subscription} from 'rxjs/Subscription'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; -import {LayoutDirective} from './layout'; +import {LayoutDirective} from '../flexbox/layout'; const FALSY = ['false', false, 0]; diff --git a/src/lib/flexbox/api/show.spec.ts b/src/lib/api/ext/show.spec.ts similarity index 100% rename from src/lib/flexbox/api/show.spec.ts rename to src/lib/api/ext/show.spec.ts diff --git a/src/lib/flexbox/api/style.spec.ts b/src/lib/api/ext/style.spec.ts similarity index 99% rename from src/lib/flexbox/api/style.spec.ts rename to src/lib/api/ext/style.spec.ts index 1378f7f66..f9fd0b8da 100644 --- a/src/lib/flexbox/api/style.spec.ts +++ b/src/lib/api/ext/style.spec.ts @@ -14,7 +14,7 @@ import {MatchMedia} from '../../media-query/match-media'; import {DEFAULT_BREAKPOINTS_PROVIDER} from '../../media-query/breakpoints/break-points-provider'; import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-registry'; -import {LayoutDirective} from './layout'; +import {LayoutDirective} from '../flexbox/layout'; import {StyleDirective} from './style'; import {MediaQueriesModule} from '../../media-query/_module'; diff --git a/src/lib/flexbox/api/style.ts b/src/lib/api/ext/style.ts similarity index 98% rename from src/lib/flexbox/api/style.ts rename to src/lib/api/ext/style.ts index 4e2a4ede3..fc9109f34 100644 --- a/src/lib/flexbox/api/style.ts +++ b/src/lib/api/ext/style.ts @@ -22,8 +22,8 @@ import { } from '@angular/core'; import {NgStyle} from '@angular/common'; -import {BaseFxDirective} from './base'; -import {BaseFxDirectiveAdapter} from './base-adapter'; +import {BaseFxDirective} from '../core/base'; +import {BaseFxDirectiveAdapter} from '../core/base-adapter'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; import {extendObject} from '../../utils/object-extend'; diff --git a/src/lib/flexbox/api/flex-align.ts b/src/lib/api/flexbox/flex-align.ts similarity index 98% rename from src/lib/flexbox/api/flex-align.ts rename to src/lib/api/flexbox/flex-align.ts index 181192642..c7a4bfe5e 100644 --- a/src/lib/flexbox/api/flex-align.ts +++ b/src/lib/api/flexbox/flex-align.ts @@ -16,7 +16,7 @@ import { SimpleChanges, } from '@angular/core'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; diff --git a/src/lib/flexbox/api/flex-fill.ts b/src/lib/api/flexbox/flex-fill.ts similarity index 95% rename from src/lib/flexbox/api/flex-fill.ts rename to src/lib/api/flexbox/flex-fill.ts index 22a42cf2b..1fc4ef26c 100644 --- a/src/lib/flexbox/api/flex-fill.ts +++ b/src/lib/api/flexbox/flex-fill.ts @@ -8,7 +8,7 @@ import {Directive, ElementRef, Renderer2} from '@angular/core'; import {MediaMonitor} from '../../media-query/media-monitor'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; const FLEX_FILL_CSS = { 'margin': 0, diff --git a/src/lib/flexbox/api/flex-offset.spec.ts b/src/lib/api/flexbox/flex-offset.spec.ts similarity index 100% rename from src/lib/flexbox/api/flex-offset.spec.ts rename to src/lib/api/flexbox/flex-offset.spec.ts diff --git a/src/lib/flexbox/api/flex-offset.ts b/src/lib/api/flexbox/flex-offset.ts similarity index 99% rename from src/lib/flexbox/api/flex-offset.ts rename to src/lib/api/flexbox/flex-offset.ts index 6b2b54076..6cf54629e 100644 --- a/src/lib/flexbox/api/flex-offset.ts +++ b/src/lib/api/flexbox/flex-offset.ts @@ -20,7 +20,7 @@ import { import {Subscription} from 'rxjs/Subscription'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; import {LayoutDirective} from './layout'; diff --git a/src/lib/flexbox/api/flex-order.ts b/src/lib/api/flexbox/flex-order.ts similarity index 98% rename from src/lib/flexbox/api/flex-order.ts rename to src/lib/api/flexbox/flex-order.ts index d2c567803..3a24dbcb4 100644 --- a/src/lib/flexbox/api/flex-order.ts +++ b/src/lib/api/flexbox/flex-order.ts @@ -16,7 +16,7 @@ import { SimpleChanges, } from '@angular/core'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; diff --git a/src/lib/flexbox/api/flex.spec.ts b/src/lib/api/flexbox/flex.spec.ts similarity index 99% rename from src/lib/flexbox/api/flex.spec.ts rename to src/lib/api/flexbox/flex.spec.ts index 2ae641b95..a35be82a9 100644 --- a/src/lib/flexbox/api/flex.spec.ts +++ b/src/lib/api/flexbox/flex.spec.ts @@ -14,8 +14,8 @@ import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-regi import {MockMatchMedia} from '../../media-query/mock/mock-match-media'; import {MatchMedia} from '../../media-query/match-media'; import {FlexLayoutModule} from '../../module'; -import {FlexDirective} from '../../flexbox/api/flex'; -import {LayoutDirective} from '../../flexbox/api/layout'; +import {FlexDirective} from '../../api/flexbox/flex'; +import {LayoutDirective} from '../../api/flexbox/layout'; import {customMatchers, expect} from '../../utils/testing/custom-matchers'; import {_dom as _} from '../../utils/testing/dom-tools'; diff --git a/src/lib/flexbox/api/flex.ts b/src/lib/api/flexbox/flex.ts similarity index 99% rename from src/lib/flexbox/api/flex.ts rename to src/lib/api/flexbox/flex.ts index 983bd54ec..b2fbf588f 100644 --- a/src/lib/flexbox/api/flex.ts +++ b/src/lib/api/flexbox/flex.ts @@ -20,7 +20,7 @@ import {Subscription} from 'rxjs/Subscription'; import {extendObject} from '../../utils/object-extend'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; diff --git a/src/lib/flexbox/api/layout-align.spec.ts b/src/lib/api/flexbox/layout-align.spec.ts similarity index 100% rename from src/lib/flexbox/api/layout-align.spec.ts rename to src/lib/api/flexbox/layout-align.spec.ts diff --git a/src/lib/flexbox/api/layout-align.ts b/src/lib/api/flexbox/layout-align.ts similarity index 99% rename from src/lib/flexbox/api/layout-align.ts rename to src/lib/api/flexbox/layout-align.ts index 665ed34b2..02bde9179 100644 --- a/src/lib/flexbox/api/layout-align.ts +++ b/src/lib/api/flexbox/layout-align.ts @@ -19,7 +19,7 @@ import { import {Subscription} from 'rxjs/Subscription'; import {extendObject} from '../../utils/object-extend'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; diff --git a/src/lib/flexbox/api/layout-gap.spec.ts b/src/lib/api/flexbox/layout-gap.spec.ts similarity index 100% rename from src/lib/flexbox/api/layout-gap.spec.ts rename to src/lib/api/flexbox/layout-gap.spec.ts diff --git a/src/lib/flexbox/api/layout-gap.ts b/src/lib/api/flexbox/layout-gap.ts similarity index 99% rename from src/lib/flexbox/api/layout-gap.ts rename to src/lib/api/flexbox/layout-gap.ts index c0f38a299..6acec5a57 100644 --- a/src/lib/flexbox/api/layout-gap.ts +++ b/src/lib/api/flexbox/layout-gap.ts @@ -20,7 +20,7 @@ import { } from '@angular/core'; import {Subscription} from 'rxjs/Subscription'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {LayoutDirective} from './layout'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; diff --git a/src/lib/flexbox/api/layout-wrap.ts b/src/lib/api/flexbox/layout-wrap.ts similarity index 99% rename from src/lib/flexbox/api/layout-wrap.ts rename to src/lib/api/flexbox/layout-wrap.ts index 48845e2bd..9b39f4fac 100644 --- a/src/lib/flexbox/api/layout-wrap.ts +++ b/src/lib/api/flexbox/layout-wrap.ts @@ -17,7 +17,7 @@ import { } from '@angular/core'; import {Subscription} from 'rxjs/Subscription'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {LayoutDirective} from './layout'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; diff --git a/src/lib/flexbox/api/layout.spec.ts b/src/lib/api/flexbox/layout.spec.ts similarity index 100% rename from src/lib/flexbox/api/layout.spec.ts rename to src/lib/api/flexbox/layout.spec.ts diff --git a/src/lib/flexbox/api/layout.ts b/src/lib/api/flexbox/layout.ts similarity index 99% rename from src/lib/flexbox/api/layout.ts rename to src/lib/api/flexbox/layout.ts index 0b4fbcc89..5754d6ece 100644 --- a/src/lib/flexbox/api/layout.ts +++ b/src/lib/api/flexbox/layout.ts @@ -18,7 +18,7 @@ import { import {BehaviorSubject} from 'rxjs/BehaviorSubject'; import {Observable} from 'rxjs/Observable'; -import {BaseFxDirective} from './base'; +import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; import {buildLayoutCSS} from '../../utils/layout-validator'; diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts new file mode 100644 index 000000000..f7ffdd5af --- /dev/null +++ b/src/lib/api/index.ts @@ -0,0 +1,28 @@ +/** + * @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 './core/base'; +export * from './core/base-adapter'; +export * from './core/responsive-activation'; + +export * from './flexbox/layout'; +export * from './flexbox/layout-align'; +export * from './flexbox/layout-gap'; +export * from './flexbox/layout-wrap'; + +export * from './flexbox/flex'; +export * from './flexbox/flex-align'; +export * from './flexbox/flex-fill'; +export * from './flexbox/flex-offset'; +export * from './flexbox/flex-order'; + +export * from './ext/class'; +export * from './ext/style'; +export * from './ext/show-hide'; +export * from './ext/img-src'; +export * from './ext/img-srcset'; + diff --git a/src/lib/flexbox/index.ts b/src/lib/flexbox/index.ts deleted file mode 100644 index 644f60c06..000000000 --- a/src/lib/flexbox/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @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 './api/base'; -export * from './api/base-adapter'; - -export * from './api/flex'; -export * from './api/flex-align'; -export * from './api/flex-fill'; -export * from './api/flex-offset'; -export * from './api/flex-order'; - -export * from './api/layout'; -export * from './api/layout-align'; -export * from './api/layout-gap'; -export * from './api/layout-wrap'; - -export * from './api/show-hide'; - -export * from './api/class'; -export * from './api/style'; - -export * from './responsive/responsive-activation'; diff --git a/src/lib/module.ts b/src/lib/module.ts index 26d7b10f0..c5e64e5f8 100644 --- a/src/lib/module.ts +++ b/src/lib/module.ts @@ -18,26 +18,28 @@ import { import {MEDIA_MONITOR_PROVIDER} from './media-query/media-monitor-provider'; import {OBSERVABLE_MEDIA_PROVIDER} from './media-query/observable-media-provider'; -import {FlexDirective} from './flexbox/api/flex'; -import {LayoutDirective} from './flexbox/api/layout'; -import {ShowHideDirective} from './flexbox/api/show-hide'; -import {FlexAlignDirective} from './flexbox/api/flex-align'; -import {FlexFillDirective} from './flexbox/api/flex-fill'; -import {FlexOffsetDirective} from './flexbox/api/flex-offset'; -import {FlexOrderDirective} from './flexbox/api/flex-order'; -import {LayoutAlignDirective} from './flexbox/api/layout-align'; -import {LayoutWrapDirective} from './flexbox/api/layout-wrap'; -import {LayoutGapDirective} from './flexbox/api/layout-gap'; -import {ClassDirective} from './flexbox/api/class'; -import {StyleDirective} from './flexbox/api/style'; -import {ImgSrcsetDirective} from './flexbox/api/img-srcset'; +import {FlexDirective} from './api/flexbox/flex'; +import {LayoutDirective} from './api/flexbox/layout'; +import {FlexAlignDirective} from './api/flexbox/flex-align'; +import {FlexFillDirective} from './api/flexbox/flex-fill'; +import {FlexOffsetDirective} from './api/flexbox/flex-offset'; +import {FlexOrderDirective} from './api/flexbox/flex-order'; +import {LayoutAlignDirective} from './api/flexbox/layout-align'; +import {LayoutWrapDirective} from './api/flexbox/layout-wrap'; +import {LayoutGapDirective} from './api/flexbox/layout-gap'; + +import {ShowHideDirective} from './api/ext/show-hide'; +import {ClassDirective} from './api/ext/class'; +import {StyleDirective} from './api/ext/style'; +import {ImgSrcDirective} from './api/ext/img-src'; +import {ImgSrcsetDirective} from './api/ext/img-srcset'; /** * Since the equivalent results are easily achieved with a css class attached to each * layout child, these have been deprecated and removed from the API. * - * import {LayoutPaddingDirective} from './flexbox/api/layout-padding'; - * import {LayoutMarginDirective} from './flexbox/api/layout-margin'; + * import {LayoutPaddingDirective} from './api/flexbox/layout-padding'; + * import {LayoutMarginDirective} from './api/flexbox/layout-margin'; */ const ALL_DIRECTIVES = [ @@ -53,7 +55,8 @@ const ALL_DIRECTIVES = [ ShowHideDirective, ClassDirective, StyleDirective, - ImgSrcsetDirective + ImgSrcsetDirective, + ImgSrcDirective ]; /** diff --git a/src/lib/public_api.ts b/src/lib/public_api.ts index 473cfd720..3808b0e5d 100644 --- a/src/lib/public_api.ts +++ b/src/lib/public_api.ts @@ -11,7 +11,9 @@ * @description * Entry point for all public APIs of Angular Flex-Layout. */ -export * from './module'; -export * from './flexbox/index'; +export * from './api/index'; export * from './media-query/index'; export * from './utils/index'; + +// Flex-Layout Module +export * from './module'; diff --git a/src/lib/utils/testing/custom-matchers.ts b/src/lib/utils/testing/custom-matchers.ts index dc63a3409..849c2ba0f 100644 --- a/src/lib/utils/testing/custom-matchers.ts +++ b/src/lib/utils/testing/custom-matchers.ts @@ -214,10 +214,10 @@ function buildCompareStyleFunction(inlineOnly = true) { allPassed = Object.keys(styles).length !== 0; Object.keys(styles).forEach(prop => { - let {elHasStyle, current} = hasPrefixedStyles(actual, prop, styles[prop], inlineOnly) + let {elHasStyle, current} = hasPrefixedStyles(actual, prop, styles[prop], inlineOnly); allPassed = allPassed && elHasStyle; if (!elHasStyle) { - extendObject(found, current) + extendObject(found, current); } }); @@ -232,7 +232,7 @@ function buildCompareStyleFunction(inlineOnly = true) { `; } }; - } + }; } /**