import { CLASS_ACTIVE, CLASS_FADE, CLASS_FIXED, CLASS_FULLSCREEN_EXIT, CLASS_HIDE, CLASS_IN, CLASS_INVISIBLE, CLASS_LOADING, CLASS_SHOW, CLASS_TRANSITION, EVENT_CLICK, EVENT_ERROR, EVENT_HIDE, EVENT_LOAD, EVENT_MOVE, EVENT_MOVED, EVENT_PLAY, EVENT_ROTATE, EVENT_ROTATED, EVENT_SCALE, EVENT_SCALED, EVENT_SHOW, EVENT_STOP, EVENT_TRANSITION_END, EVENT_VIEW, EVENT_VIEWED, EVENT_ZOOM, EVENT_ZOOMED, NAMESPACE, } from './constants'; import { addClass, addListener, assign, dispatchEvent, escapeHTMLEntities, forEach, getData, getOffset, getPointersCenter, hasClass, isFunction, isNumber, isPlainObject, isUndefined, removeClass, removeListener, setStyle, toggleClass, } from './utilities'; export default { /** Show the viewer (only available in modal mode) * @param {boolean} [immediate=false] - Indicates if show the viewer immediately or not. * @returns {Viewer} this */ show(immediate = false) { const { element, options } = this; if (options.inline || this.showing || this.isShown || this.showing) { return this; } if (!this.ready) { this.build(); if (this.ready) { this.show(immediate); } return this; } if (isFunction(options.show)) { addListener(element, EVENT_SHOW, options.show, { once: true, }); } if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) { return this; } if (this.hiding) { this.transitioning.abort(); } this.showing = true; this.open(); const { viewer } = this; removeClass(viewer, CLASS_HIDE); viewer.setAttribute('role', 'dialog'); viewer.setAttribute('aria-labelledby', this.title.id); viewer.setAttribute('aria-modal', true); viewer.removeAttribute('aria-hidden'); if (options.transition && !immediate) { const shown = this.shown.bind(this); this.transitioning = { abort: () => { removeListener(viewer, EVENT_TRANSITION_END, shown); removeClass(viewer, CLASS_IN); }, }; addClass(viewer, CLASS_TRANSITION); // Force reflow to enable CSS3 transition viewer.initialOffsetWidth = viewer.offsetWidth; addListener(viewer, EVENT_TRANSITION_END, shown, { once: true, }); addClass(viewer, CLASS_IN); } else { addClass(viewer, CLASS_IN); this.shown(); } return this; }, /** * Hide the viewer (only available in modal mode) * @param {boolean} [immediate=false] - Indicates if hide the viewer immediately or not. * @returns {Viewer} this */ hide(immediate = false) { const { element, options } = this; if (options.inline || this.hiding || !(this.isShown || this.showing)) { return this; } if (isFunction(options.hide)) { addListener(element, EVENT_HIDE, options.hide, { once: true, }); } if (dispatchEvent(element, EVENT_HIDE) === false) { return this; } if (this.showing) { this.transitioning.abort(); } this.hiding = true; if (this.played) { this.stop(); } else if (this.viewing) { this.viewing.abort(); } const { viewer, image } = this; const hideImmediately = () => { removeClass(viewer, CLASS_IN); this.hidden(); }; if (options.transition && !immediate) { const onViewerTransitionEnd = (event) => { // Ignore all propagating `transitionend` events (#275). if (event && event.target === viewer) { removeListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd); this.hidden(); } }; const onImageTransitionEnd = () => { // In case of show the viewer by `viewer.show(true)` previously (#407). if (hasClass(viewer, CLASS_TRANSITION)) { addListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd); removeClass(viewer, CLASS_IN); } else { hideImmediately(); } }; this.transitioning = { abort: () => { if (this.viewed && hasClass(image, CLASS_TRANSITION)) { removeListener(image, EVENT_TRANSITION_END, onImageTransitionEnd); } else if (hasClass(viewer, CLASS_TRANSITION)) { removeListener(viewer, EVENT_TRANSITION_END, onViewerTransitionEnd); } }, }; // In case of hiding the viewer when holding on the image (#255), // note that the `CLASS_TRANSITION` class will be removed on pointer down. if (this.viewed && hasClass(image, CLASS_TRANSITION)) { addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, { once: true, }); this.zoomTo(0, false, null, null, true); } else { onImageTransitionEnd(); } } else { hideImmediately(); } return this; }, /** * View one of the images with image's index * @param {number} index - The index of the image to view. * @returns {Viewer} this */ view(index = this.options.initialViewIndex) { index = Number(index) || 0; if (this.hiding || this.played || index < 0 || index >= this.length || (this.viewed && index === this.index)) { return this; } if (!this.isShown) { this.index = index; return this.show(); } if (this.viewing) { this.viewing.abort(); } const { element, options, title, canvas, } = this; const item = this.items[index]; const img = item.querySelector('img'); const url = getData(img, 'originalUrl'); const alt = img.getAttribute('alt'); const image = document.createElement('img'); forEach(options.inheritedAttributes, (name) => { const value = img.getAttribute(name); if (value !== null) { image.setAttribute(name, value); } }); image.src = url; image.alt = alt; if (isFunction(options.view)) { addListener(element, EVENT_VIEW, options.view, { once: true, }); } if (dispatchEvent(element, EVENT_VIEW, { originalImage: this.images[index], index, image, }) === false || !this.isShown || this.hiding || this.played) { return this; } const activeItem = this.items[this.index]; if (activeItem) { removeClass(activeItem, CLASS_ACTIVE); activeItem.removeAttribute('aria-selected'); } addClass(item, CLASS_ACTIVE); item.setAttribute('aria-selected', true); if (options.focus) { item.focus(); } this.image = image; this.viewed = false; this.index = index; this.imageData = {}; addClass(image, CLASS_INVISIBLE); if (options.loading) { addClass(canvas, CLASS_LOADING); } canvas.innerHTML = ''; canvas.appendChild(image); // Center current item this.renderList(); // Clear title title.innerHTML = ''; // Generate title after viewed const onViewed = () => { const { imageData } = this; const render = Array.isArray(options.title) ? options.title[1] : options.title; title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(this, image, imageData) : `${alt} (${imageData.naturalWidth} × ${imageData.naturalHeight})`); }; let onLoad; let onError; addListener(element, EVENT_VIEWED, onViewed, { once: true, }); this.viewing = { abort: () => { removeListener(element, EVENT_VIEWED, onViewed); if (image.complete) { if (this.imageRendering) { this.imageRendering.abort(); } else if (this.imageInitializing) { this.imageInitializing.abort(); } } else { // Cancel download to save bandwidth. image.src = ''; removeListener(image, EVENT_LOAD, onLoad); if (this.timeout) { clearTimeout(this.timeout); } } }, }; if (image.complete) { this.load(); } else { addListener(image, EVENT_LOAD, onLoad = () => { removeListener(image, EVENT_ERROR, onError); this.load(); }, { once: true, }); addListener(image, EVENT_ERROR, onError = () => { removeListener(image, EVENT_LOAD, onLoad); if (this.timeout) { clearTimeout(this.timeout); this.timeout = false; } removeClass(image, CLASS_INVISIBLE); if (options.loading) { removeClass(this.canvas, CLASS_LOADING); } }, { once: true, }); if (this.timeout) { clearTimeout(this.timeout); } // Make the image visible if it fails to load within 1s this.timeout = setTimeout(() => { removeClass(image, CLASS_INVISIBLE); this.timeout = false; }, 1000); } return this; }, /** * View the previous image * @param {boolean} [loop=false] - Indicate if view the last one * when it is the first one at present. * @returns {Viewer} this */ prev(loop = false) { let index = this.index - 1; if (index < 0) { index = loop ? this.length - 1 : 0; } this.view(index); return this; }, /** * View the next image * @param {boolean} [loop=false] - Indicate if view the first one * when it is the last one at present. * @returns {Viewer} this */ next(loop = false) { const maxIndex = this.length - 1; let index = this.index + 1; if (index > maxIndex) { index = loop ? 0 : maxIndex; } this.view(index); return this; }, /** * Move the image with relative offsets. * @param {number} x - The moving distance in the horizontal direction. * @param {number} [y=x] The moving distance in the vertical direction. * @returns {Viewer} this */ move(x, y = x) { const { imageData } = this; this.moveTo( isUndefined(x) ? x : imageData.x + Number(x), isUndefined(y) ? y : imageData.y + Number(y), ); return this; }, /** * Move the image to an absolute point. * @param {number} x - The new position in the horizontal direction. * @param {number} [y=x] - The new position in the vertical direction. * @param {Event} [_originalEvent=null] - The original event if any. * @returns {Viewer} this */ moveTo(x, y = x, _originalEvent = null) { const { element, options, imageData } = this; x = Number(x); y = Number(y); if (this.viewed && !this.played && options.movable) { const oldX = imageData.x; const oldY = imageData.y; let changed = false; if (isNumber(x)) { changed = true; } else { x = oldX; } if (isNumber(y)) { changed = true; } else { y = oldY; } if (changed) { if (isFunction(options.move)) { addListener(element, EVENT_MOVE, options.move, { once: true, }); } if (dispatchEvent(element, EVENT_MOVE, { x, y, oldX, oldY, originalEvent: _originalEvent, }) === false) { return this; } imageData.x = x; imageData.y = y; imageData.left = x; imageData.top = y; this.moving = true; this.renderImage(() => { this.moving = false; if (isFunction(options.moved)) { addListener(element, EVENT_MOVED, options.moved, { once: true, }); } dispatchEvent(element, EVENT_MOVED, { x, y, oldX, oldY, originalEvent: _originalEvent, }, { cancelable: false, }); }); } } return this; }, /** * Rotate the image with a relative degree. * @param {number} degree - The rotate degree. * @returns {Viewer} this */ rotate(degree) { this.rotateTo((this.imageData.rotate || 0) + Number(degree)); return this; }, /** * Rotate the image to an absolute degree. * @param {number} degree - The rotate degree. * @returns {Viewer} this */ rotateTo(degree) { const { element, options, imageData } = this; degree = Number(degree); if (isNumber(degree) && this.viewed && !this.played && options.rotatable) { const oldDegree = imageData.rotate; if (isFunction(options.rotate)) { addListener(element, EVENT_ROTATE, options.rotate, { once: true, }); } if (dispatchEvent(element, EVENT_ROTATE, { degree, oldDegree, }) === false) { return this; } imageData.rotate = degree; this.rotating = true; this.renderImage(() => { this.rotating = false; if (isFunction(options.rotated)) { addListener(element, EVENT_ROTATED, options.rotated, { once: true, }); } dispatchEvent(element, EVENT_ROTATED, { degree, oldDegree, }, { cancelable: false, }); }); } return this; }, /** * Scale the image on the x-axis. * @param {number} scaleX - The scale ratio on the x-axis. * @returns {Viewer} this */ scaleX(scaleX) { this.scale(scaleX, this.imageData.scaleY); return this; }, /** * Scale the image on the y-axis. * @param {number} scaleY - The scale ratio on the y-axis. * @returns {Viewer} this */ scaleY(scaleY) { this.scale(this.imageData.scaleX, scaleY); return this; }, /** * Scale the image. * @param {number} scaleX - The scale ratio on the x-axis. * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis. * @returns {Viewer} this */ scale(scaleX, scaleY = scaleX) { const { element, options, imageData } = this; scaleX = Number(scaleX); scaleY = Number(scaleY); if (this.viewed && !this.played && options.scalable) { const oldScaleX = imageData.scaleX; const oldScaleY = imageData.scaleY; let changed = false; if (isNumber(scaleX)) { changed = true; } else { scaleX = oldScaleX; } if (isNumber(scaleY)) { changed = true; } else { scaleY = oldScaleY; } if (changed) { if (isFunction(options.scale)) { addListener(element, EVENT_SCALE, options.scale, { once: true, }); } if (dispatchEvent(element, EVENT_SCALE, { scaleX, scaleY, oldScaleX, oldScaleY, }) === false) { return this; } imageData.scaleX = scaleX; imageData.scaleY = scaleY; this.scaling = true; this.renderImage(() => { this.scaling = false; if (isFunction(options.scaled)) { addListener(element, EVENT_SCALED, options.scaled, { once: true, }); } dispatchEvent(element, EVENT_SCALED, { scaleX, scaleY, oldScaleX, oldScaleY, }, { cancelable: false, }); }); } } return this; }, /** * Zoom the image with a relative ratio. * @param {number} ratio - The target ratio. * @param {boolean} [showTooltip=false] - Indicates whether to show the tooltip. * @param {Object} [pivot] - The pivot point coordinate for zooming. * @param {Event} [_originalEvent=null] - The original event if any. * @returns {Viewer} this */ zoom(ratio, showTooltip = false, pivot = null, _originalEvent = null) { const { imageData } = this; ratio = Number(ratio); if (ratio < 0) { ratio = 1 / (1 - ratio); } else { ratio = 1 + ratio; } this.zoomTo( (imageData.width * ratio) / imageData.naturalWidth, showTooltip, pivot, _originalEvent, ); return this; }, /** * Zoom the image to an absolute ratio. * @param {number} ratio - The target ratio. * @param {boolean} [showTooltip] - Indicates whether to show the tooltip. * @param {Object} [pivot] - The pivot point coordinate for zooming. * @param {Event} [_originalEvent=null] - The original event if any. * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not. * @returns {Viewer} this */ zoomTo(ratio, showTooltip = false, pivot = null, _originalEvent = null, _zoomable = false) { const { element, options, pointers, imageData, } = this; const { x, y, width, height, naturalWidth, naturalHeight, } = imageData; ratio = Math.max(0, ratio); if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) { if (!_zoomable) { const minZoomRatio = Math.max(0.01, options.minZoomRatio); const maxZoomRatio = Math.min(100, options.maxZoomRatio); ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio); } if (_originalEvent) { switch (_originalEvent.type) { case 'wheel': if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) { ratio = 1; } break; case 'pointermove': case 'touchmove': case 'mousemove': if (ratio > 0.99 && ratio < 1.01) { ratio = 1; } break; default: } } const newWidth = naturalWidth * ratio; const newHeight = naturalHeight * ratio; const offsetWidth = newWidth - width; const offsetHeight = newHeight - height; const oldRatio = imageData.ratio; if (isFunction(options.zoom)) { addListener(element, EVENT_ZOOM, options.zoom, { once: true, }); } if (dispatchEvent(element, EVENT_ZOOM, { ratio, oldRatio, originalEvent: _originalEvent, }) === false) { return this; } this.zooming = true; if (_originalEvent) { const offset = getOffset(this.viewer); const center = pointers && Object.keys(pointers).length > 0 ? getPointersCenter(pointers) : { pageX: _originalEvent.pageX, pageY: _originalEvent.pageY, }; // Zoom from the triggering point of the event imageData.x -= offsetWidth * (((center.pageX - offset.left) - x) / width); imageData.y -= offsetHeight * (((center.pageY - offset.top) - y) / height); } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) { imageData.x -= offsetWidth * ((pivot.x - x) / width); imageData.y -= offsetHeight * ((pivot.y - y) / height); } else { // Zoom from the center of the image imageData.x -= offsetWidth / 2; imageData.y -= offsetHeight / 2; } imageData.left = imageData.x; imageData.top = imageData.y; imageData.width = newWidth; imageData.height = newHeight; imageData.oldRatio = oldRatio; imageData.ratio = ratio; this.renderImage(() => { this.zooming = false; if (isFunction(options.zoomed)) { addListener(element, EVENT_ZOOMED, options.zoomed, { once: true, }); } dispatchEvent(element, EVENT_ZOOMED, { ratio, oldRatio, originalEvent: _originalEvent, }, { cancelable: false, }); }); if (showTooltip) { this.tooltip(); } } return this; }, /** * Play the images * @param {boolean|FullscreenOptions} [fullscreen=false] - Indicate if request fullscreen or not. * @returns {Viewer} this */ play(fullscreen = false) { if (!this.isShown || this.played) { return this; } const { element, options } = this; if (isFunction(options.play)) { addListener(element, EVENT_PLAY, options.play, { once: true, }); } if (dispatchEvent(element, EVENT_PLAY) === false) { return this; } const { player } = this; const onLoad = this.loadImage.bind(this); const list = []; let total = 0; let index = 0; this.played = true; this.onLoadWhenPlay = onLoad; if (fullscreen) { this.requestFullscreen(fullscreen); } addClass(player, CLASS_SHOW); forEach(this.items, (item, i) => { const img = item.querySelector('img'); const image = document.createElement('img'); image.src = getData(img, 'originalUrl'); image.alt = img.getAttribute('alt'); image.referrerPolicy = img.referrerPolicy; total += 1; addClass(image, CLASS_FADE); toggleClass(image, CLASS_TRANSITION, options.transition); if (hasClass(item, CLASS_ACTIVE)) { addClass(image, CLASS_IN); index = i; } list.push(image); addListener(image, EVENT_LOAD, onLoad, { once: true, }); player.appendChild(image); }); if (isNumber(options.interval) && options.interval > 0) { const prev = () => { clearTimeout(this.playing.timeout); removeClass(list[index], CLASS_IN); index -= 1; index = index >= 0 ? index : total - 1; addClass(list[index], CLASS_IN); this.playing.timeout = setTimeout(prev, options.interval); }; const next = () => { clearTimeout(this.playing.timeout); removeClass(list[index], CLASS_IN); index += 1; index = index < total ? index : 0; addClass(list[index], CLASS_IN); this.playing.timeout = setTimeout(next, options.interval); }; if (total > 1) { this.playing = { prev, next, timeout: setTimeout(next, options.interval), }; } } return this; }, // Stop play stop() { if (!this.played) { return this; } const { element, options } = this; if (isFunction(options.stop)) { addListener(element, EVENT_STOP, options.stop, { once: true, }); } if (dispatchEvent(element, EVENT_STOP) === false) { return this; } const { player } = this; clearTimeout(this.playing.timeout); this.playing = false; this.played = false; forEach(player.getElementsByTagName('img'), (image) => { removeListener(image, EVENT_LOAD, this.onLoadWhenPlay); }); removeClass(player, CLASS_SHOW); player.innerHTML = ''; this.exitFullscreen(); return this; }, // Enter modal mode (only available in inline mode) full() { const { options, viewer, image, list, } = this; if (!this.isShown || this.played || this.fulled || !options.inline) { return this; } this.fulled = true; this.open(); addClass(this.button, CLASS_FULLSCREEN_EXIT); if (options.transition) { removeClass(list, CLASS_TRANSITION); if (this.viewed) { removeClass(image, CLASS_TRANSITION); } } addClass(viewer, CLASS_FIXED); viewer.setAttribute('role', 'dialog'); viewer.setAttribute('aria-labelledby', this.title.id); viewer.setAttribute('aria-modal', true); viewer.removeAttribute('style'); setStyle(viewer, { zIndex: options.zIndex, }); if (options.focus) { this.enforceFocus(); } this.initContainer(); this.viewerData = assign({}, this.containerData); this.renderList(); if (this.viewed) { this.initImage(() => { this.renderImage(() => { if (options.transition) { setTimeout(() => { addClass(image, CLASS_TRANSITION); addClass(list, CLASS_TRANSITION); }, 0); } }); }); } return this; }, // Exit modal mode (only available in inline mode) exit() { const { options, viewer, image, list, } = this; if (!this.isShown || this.played || !this.fulled || !options.inline) { return this; } this.fulled = false; this.close(); removeClass(this.button, CLASS_FULLSCREEN_EXIT); if (options.transition) { removeClass(list, CLASS_TRANSITION); if (this.viewed) { removeClass(image, CLASS_TRANSITION); } } if (options.focus) { this.clearEnforceFocus(); } viewer.removeAttribute('role'); viewer.removeAttribute('aria-labelledby'); viewer.removeAttribute('aria-modal'); removeClass(viewer, CLASS_FIXED); setStyle(viewer, { zIndex: options.zIndexInline, }); this.viewerData = assign({}, this.parentData); this.renderViewer(); this.renderList(); if (this.viewed) { this.initImage(() => { this.renderImage(() => { if (options.transition) { setTimeout(() => { addClass(image, CLASS_TRANSITION); addClass(list, CLASS_TRANSITION); }, 0); } }); }); } return this; }, // Show the current ratio of the image with percentage tooltip() { const { options, tooltipBox, imageData } = this; if (!this.viewed || this.played || !options.tooltip) { return this; } tooltipBox.textContent = `${Math.round(imageData.ratio * 100)}%`; if (!this.tooltipping) { if (options.transition) { if (this.fading) { dispatchEvent(tooltipBox, EVENT_TRANSITION_END); } addClass(tooltipBox, CLASS_SHOW); addClass(tooltipBox, CLASS_FADE); addClass(tooltipBox, CLASS_TRANSITION); tooltipBox.removeAttribute('aria-hidden'); // Force reflow to enable CSS3 transition tooltipBox.initialOffsetWidth = tooltipBox.offsetWidth; addClass(tooltipBox, CLASS_IN); } else { addClass(tooltipBox, CLASS_SHOW); tooltipBox.removeAttribute('aria-hidden'); } } else { clearTimeout(this.tooltipping); } this.tooltipping = setTimeout(() => { if (options.transition) { addListener(tooltipBox, EVENT_TRANSITION_END, () => { removeClass(tooltipBox, CLASS_SHOW); removeClass(tooltipBox, CLASS_FADE); removeClass(tooltipBox, CLASS_TRANSITION); tooltipBox.setAttribute('aria-hidden', true); this.fading = false; }, { once: true, }); removeClass(tooltipBox, CLASS_IN); this.fading = true; } else { removeClass(tooltipBox, CLASS_SHOW); tooltipBox.setAttribute('aria-hidden', true); } this.tooltipping = false; }, 1000); return this; }, /** * Toggle the image size between its current size and natural size * @param {Event} [_originalEvent=null] - The original event if any. * @returns {Viewer} this */ toggle(_originalEvent = null) { if (this.imageData.ratio === 1) { this.zoomTo(this.imageData.oldRatio, true, null, _originalEvent); } else { this.zoomTo(1, true, null, _originalEvent); } return this; }, // Reset the image to its initial state reset() { if (this.viewed && !this.played) { this.imageData = assign({}, this.initialImageData); this.renderImage(); } return this; }, // Update viewer when images changed update() { const { element, options, isImg } = this; // Destroy viewer if the target image was deleted if (isImg && !element.parentNode) { return this.destroy(); } const images = []; forEach(isImg ? [element] : element.querySelectorAll('img'), (image) => { if (isFunction(options.filter)) { if (options.filter.call(this, image)) { images.push(image); } } else if (this.getImageURL(image)) { images.push(image); } }); if (!images.length) { return this; } this.images = images; this.length = images.length; if (this.ready) { const changedIndexes = []; forEach(this.items, (item, i) => { const img = item.querySelector('img'); const image = images[i]; if (image && img) { if ( image.src !== img.src // Title changed (#408) || image.alt !== img.alt ) { changedIndexes.push(i); } } else { changedIndexes.push(i); } }); setStyle(this.list, { width: 'auto', }); this.initList(); if (this.isShown) { if (this.length) { if (this.viewed) { const changedIndex = changedIndexes.indexOf(this.index); if (changedIndex >= 0) { this.viewed = false; this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0)); } else { const activeItem = this.items[this.index]; // Reactivate the current viewing item after reset the list. addClass(activeItem, CLASS_ACTIVE); activeItem.setAttribute('aria-selected', true); } } } else { this.image = null; this.viewed = false; this.index = 0; this.imageData = {}; this.canvas.innerHTML = ''; this.title.innerHTML = ''; } } } else { this.build(); } return this; }, // Destroy the viewer destroy() { const { element, options } = this; if (!element[NAMESPACE]) { return this; } this.destroyed = true; if (this.ready) { if (this.played) { this.stop(); } if (options.inline) { if (this.fulled) { this.exit(); } this.unbind(); } else if (this.isShown) { if (this.viewing) { if (this.imageRendering) { this.imageRendering.abort(); } else if (this.imageInitializing) { this.imageInitializing.abort(); } } if (this.hiding) { this.transitioning.abort(); } this.hidden(); } else if (this.showing) { this.transitioning.abort(); this.hidden(); } this.ready = false; this.viewer.parentNode.removeChild(this.viewer); } else if (options.inline) { if (this.delaying) { this.delaying.abort(); } else if (this.initializing) { this.initializing.abort(); } } if (!options.inline) { removeListener(element, EVENT_CLICK, this.onStart); } element[NAMESPACE] = undefined; return this; }, };