@exadel/esl
Version:
Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components
307 lines (306 loc) • 10.7 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var ESLImage_1;
import { ExportNs } from '../../esl-utils/environment/export-ns';
import { bind, prop, attr, boolAttr } from '../../esl-utils/decorators';
import { CSSClassUtils } from '../../esl-utils/dom/class';
import { ESLBaseElement } from '../../esl-base-element/core';
import { ESLMediaRuleList } from '../../esl-media-query/core';
import { getIObserver } from './esl-image-iobserver';
import { EMPTY_IMAGE, STRATEGIES, isEmptyImage } from './esl-image-strategies';
const isLoadState = (state) => ['error', 'loaded', 'ready'].includes(state);
/**
* ESLImage - custom element, that provides flexible ways to include images on web pages.
* Was originally developed as an alternative to `<picture>` element, but with more features inside.
*
* @author Alexey Stsefanovich (ala'n), Yuliya Adamskaya
* @deprecated use native `<picture>` element or `<img>` with `srcset`, `sizes` and `loading` attributes.
* For additional sugar, see modern esl-image-utils package.
*/
let ESLImage = ESLImage_1 = class ESLImage extends ESLBaseElement {
connectedCallback() {
super.connectedCallback();
this.alt =
this.alt || this.getAttribute('aria-label') || this.getAttribute('data-alt') || '';
this.updateA11y();
this.srcRules = ESLMediaRuleList.parseQuery(this.src);
if (this.lazyObservable) {
this.removeAttribute('lazy-triggered');
getIObserver().observe(this);
this._detachLazyTrigger = function () {
getIObserver().unobserve(this);
this._detachLazyTrigger = null;
};
}
this.refresh();
}
disconnectedCallback() {
this.clearImage();
super.disconnectedCallback();
this._detachLazyTrigger && this._detachLazyTrigger();
if (this._srcRules) {
this._srcRules.removeEventListener(this._onMediaMatchChange);
}
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (!this.connected || oldVal === newVal)
return;
switch (attrName) {
case 'aria-label':
this.alt = newVal || '';
break;
case 'alt':
case 'role':
this.updateA11y();
break;
case 'data-src':
this.srcRules = ESLMediaRuleList.parseQuery(newVal);
this.refresh();
break;
case 'data-src-base':
this.refresh();
break;
case 'mode':
this.changeMode(oldVal, newVal);
break;
case 'lazy-triggered':
this.lazyTriggered && this.update();
break;
}
}
get srcRules() {
if (!this._srcRules) {
this.srcRules = ESLMediaRuleList.parseQuery(this.src);
}
return this._srcRules;
}
set srcRules(rules) {
if (this._srcRules) {
this._srcRules.removeEventListener(this._onMediaMatchChange);
}
this._srcRules = rules;
this._srcRules.addEventListener(this._onMediaMatchChange);
}
get currentSrc() {
return this._currentSrc;
}
get empty() {
return !this._currentSrc || isEmptyImage(this._currentSrc);
}
get canUpdate() {
return this.lazyTriggered || this.lazy === 'none';
}
get lazyObservable() {
return this.lazy !== 'none' && this.lazy !== 'manual';
}
get originalWidth() {
return this._shadowImageElement ? this._shadowImageElement.width : 0;
}
get originalHeight() {
return this._shadowImageElement ? this._shadowImageElement.height : 0;
}
triggerLoad() {
this.setAttribute('lazy-triggered', '');
}
changeMode(oldVal, newVal) {
oldVal = oldVal || 'save-ratio';
newVal = newVal || 'save-ratio';
if (oldVal === newVal)
return;
if (!STRATEGIES[newVal]) {
this.mode = oldVal;
throw new Error('ESL Image: Unsupported mode: ' + newVal);
}
this.clearImage();
if (this.loaded)
this.syncImage();
}
update(force = false) {
if (!this.canUpdate)
return;
const src = this.getPath(this.srcRules.activeValue);
if (this._currentSrc !== src || !this.ready || force) {
this._currentSrc = src;
this._shadowImg.src = src;
if (this.refreshOnUpdate || !this.ready) {
this.syncImage();
}
if (this._shadowImg.complete) {
this._shadowImgError ? this._onError() : this._onLoad();
}
}
this._detachLazyTrigger && this._detachLazyTrigger();
}
updateA11y() {
const role = this.getAttribute('role') || 'img';
this.setAttribute('role', role);
this.innerImage && (this.innerImage.alt = this.alt);
if (role === 'img')
this.setAttribute('aria-label', this.alt);
}
getPath(src) {
if (!src || src === '0' || src === 'none') {
return ESLImage_1.EMPTY_IMAGE;
}
return this.srcBase + src;
}
refresh() {
this.removeAttribute('loaded');
this.removeAttribute('ready');
this.updateContainerClasses();
this.clearImage();
this.update(true);
}
syncImage() {
const strategy = STRATEGIES[this.mode];
this._strategy = strategy;
strategy && strategy.apply(this, this._shadowImg);
}
clearImage() {
this._strategy && this._strategy.clear(this);
this._strategy = null;
}
get innerImage() {
return this._innerImg;
}
attachInnerImage() {
if (!this._innerImg) {
this._innerImg = this.querySelector(`img.${this.innerImageClass}`) ||
this._shadowImg.cloneNode();
this._innerImg.className = this.innerImageClass;
}
if (!this._innerImg.parentNode) {
this.appendChild(this._innerImg);
}
this._innerImg.alt = this.alt;
return this._innerImg;
}
removeInnerImage() {
if (!this.innerImage)
return;
this.removeChild(this.innerImage);
setTimeout(() => {
if (this._innerImg && !this._innerImg.parentNode) {
this._innerImg = null;
}
});
}
get _shadowImg() {
if (!this._shadowImageElement) {
this._shadowImageElement = new Image();
this._shadowImageElement.onload = this._onLoad;
this._shadowImageElement.onerror = this._onError;
}
return this._shadowImageElement;
}
get _shadowImgError() {
if (!this._shadowImg.complete)
return false;
if (this._shadowImg.src.slice(-4) === '.svg')
return false;
return this._shadowImg.naturalHeight <= 0;
}
_onLoad() {
this.syncImage();
this._onReadyState(true);
this.updateContainerClasses();
}
_onError() {
this._onReadyState(false);
this.updateContainerClasses();
}
_onMediaMatchChange() {
this.update();
}
_onReadyState(successful) {
if (this.ready)
return;
this.toggleAttribute('loaded', successful);
this.toggleAttribute('error', !successful);
this.toggleAttribute('ready', true);
this.$$fire(successful ? this.LOAD_EVENT : this.ERROR_EVENT, { bubbles: false });
this.$$fire(this.READY_EVENT, { bubbles: false });
}
updateContainerClasses() {
if (this.containerClass === null)
return;
const cls = this.containerClass || this.constructor.DEFAULT_CONTAINER_CLS;
const state = isLoadState(this.containerClassState) && this[this.containerClassState];
const targetEl = this.$$find(this.containerClassTarget);
targetEl && CSSClassUtils.toggle(targetEl, cls, state);
}
};
ESLImage.is = 'esl-image';
ESLImage.observedAttributes = ['alt', 'role', 'mode', 'aria-label', 'data-src', 'data-src-base', 'lazy-triggered'];
/** Default container class value */
ESLImage.DEFAULT_CONTAINER_CLS = 'img-container-loaded';
ESLImage.STRATEGIES = STRATEGIES;
ESLImage.EMPTY_IMAGE = EMPTY_IMAGE;
__decorate([
prop('ready')
], ESLImage.prototype, "READY_EVENT", void 0);
__decorate([
prop('load')
], ESLImage.prototype, "LOAD_EVENT", void 0);
__decorate([
prop('error')
], ESLImage.prototype, "ERROR_EVENT", void 0);
__decorate([
attr()
], ESLImage.prototype, "alt", void 0);
__decorate([
attr({ defaultValue: 'save-ratio' })
], ESLImage.prototype, "mode", void 0);
__decorate([
attr({ dataAttr: true })
], ESLImage.prototype, "src", void 0);
__decorate([
attr({ dataAttr: true })
], ESLImage.prototype, "srcBase", void 0);
__decorate([
attr({ defaultValue: 'none' })
], ESLImage.prototype, "lazy", void 0);
__decorate([
boolAttr()
], ESLImage.prototype, "lazyTriggered", void 0);
__decorate([
boolAttr()
], ESLImage.prototype, "refreshOnUpdate", void 0);
__decorate([
attr({ defaultValue: 'inner-image' })
], ESLImage.prototype, "innerImageClass", void 0);
__decorate([
attr({ defaultValue: null })
], ESLImage.prototype, "containerClass", void 0);
__decorate([
attr({ defaultValue: '::parent' })
], ESLImage.prototype, "containerClassTarget", void 0);
__decorate([
attr({ defaultValue: 'ready' })
], ESLImage.prototype, "containerClassState", void 0);
__decorate([
boolAttr({ readonly: true })
], ESLImage.prototype, "ready", void 0);
__decorate([
boolAttr({ readonly: true })
], ESLImage.prototype, "loaded", void 0);
__decorate([
boolAttr({ readonly: true })
], ESLImage.prototype, "error", void 0);
__decorate([
bind
], ESLImage.prototype, "_onLoad", null);
__decorate([
bind
], ESLImage.prototype, "_onError", null);
__decorate([
bind
], ESLImage.prototype, "_onMediaMatchChange", null);
ESLImage = ESLImage_1 = __decorate([
ExportNs('Image')
], ESLImage);
export { ESLImage };