UNPKG

@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
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 };