This repository has been archived by the owner on Jan 6, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 772
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api, img-src): add ImgSrcDirective for responsive API for img el…
…ements * add responsive API to img.src: src.md, src.lt-lg, src.gt-xs, etc. * repackage API classes to easily distinguish flexbox APIs and extended responsive APIs * fix ImgSrcsetDirective to support usages without `<picture>` parents Closes #366, Fixes #81, Fixes #376.
- Loading branch information
1 parent
8f93600
commit 15bdecb
Showing
35 changed files
with
530 additions
and
122 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<any>; | ||
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(` | ||
<img src="https://dummyimage.com/300x300/c72538/ffffff.png"> | ||
`); | ||
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(` | ||
<img [src]="defaultSrc" [src.xs]="xsSrc"> | ||
`); | ||
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(` | ||
<img src="" > | ||
`); | ||
|
||
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(` | ||
<img [src]="xsSrc" | ||
[src.md]="mdSrc"> | ||
`); | ||
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(` | ||
<img [src.md]="mdSrc"> | ||
`); | ||
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]; | ||
|
||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <img> '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. | ||
* <img src="defaultScene.jpg" src.xs="mobileScene.jpg"></img> | ||
* | ||
* @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 <img> 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 || ''; | ||
} | ||
} |
Oops, something went wrong.