lightgallery
Version:
lightGallery is a feature-rich, modular JavaScript gallery plugin for building beautiful image and video galleries for the web and the mobile
3,806 lines • 137 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.lightGallery = factory());
})(this, function() {
"use strict";/*!
* lightgallery | 3.0.0 | October 9th 2026
* http://www.lightgalleryjs.com/
* Copyright (c) 2020 Sachin Neravath;
* @license GPLv3
*/
function getVideoInfo(src, hasHtml5Video) {
if (!src) {
return hasHtml5Video ? { html5: true } : void 0;
}
const youtube = src.match(
/\/\/(?:www\.)?youtu(?:\.be|be\.com|be-nocookie\.com)\/(?:watch\?v=|embed\/)?([a-z0-9\-_%]+)([&|?][\S]*)*/i
);
if (youtube) {
return { youtube: [...youtube] };
}
const vimeo = src.match(
/\/\/(?:www\.)?(?:player\.)?vimeo.com\/(?:video\/)?([0-9a-z\-_]+)(.*)?/i
);
if (vimeo) {
return { vimeo: [...vimeo] };
}
const wistia = src.match(
/https?:\/\/(.+)?(wistia\.com|wi\.st)\/(medias|embed)\/([0-9a-z\-_]+)(.*)/
);
if (wistia) {
return { wistia: [...wistia] };
}
return hasHtml5Video ? { html5: true } : void 0;
}
function getYouTubePosterUrl(videoInfo) {
if (!(videoInfo == null ? void 0 : videoInfo.youtube)) {
return void 0;
}
return `//img.youtube.com/vi/${videoInfo.youtube[1]}/maxresdefault.jpg`;
}
function getFacadePoster(item, videoInfo, loadYouTubePoster) {
if (!videoInfo || videoInfo.html5) {
return item.poster || void 0;
}
return item.poster || (loadYouTubePoster ? getYouTubePosterUrl(videoInfo) : void 0) || item.thumb || void 0;
}
const TESTING_LICENSE_KEY = "0000-0000-000-0000";
const LICENSE_KEY_PREFIX = "LIG";
const LICENSE_DOCS = "https://www.lightgalleryjs.com/docs/license/";
function checkLicenseKey(licenseKey) {
const key = (licenseKey != null ? licenseKey : "").trim();
if (!key) {
return {
level: "error",
message: `lightGallery: please provide a valid license key. See ${LICENSE_DOCS}`
};
}
if (key === TESTING_LICENSE_KEY) {
return {
level: "warn",
message: `lightGallery: ${key} license key is not valid for production use. See ${LICENSE_DOCS}`
};
}
if (!key.startsWith(LICENSE_KEY_PREFIX)) {
return {
level: "warn",
message: `lightGallery: this license key is for v1 or v2 and is not valid for v3. Please upgrade to a v3 license. See ${LICENSE_DOCS}`
};
}
return null;
}
let pageLicenseKey;
const loggedNotices = /* @__PURE__ */ new Set();
function setLicenseKey(licenseKey) {
pageLicenseKey = licenseKey;
}
function resolveLicenseKey(instanceKey) {
const own = (instanceKey != null ? instanceKey : "").trim();
if (pageLicenseKey !== void 0 && (!own || own === TESTING_LICENSE_KEY)) {
return pageLicenseKey;
}
return instanceKey != null ? instanceKey : TESTING_LICENSE_KEY;
}
function takeLicenseNotice(instanceKey) {
const notice = checkLicenseKey(resolveLicenseKey(instanceKey));
if (!notice || loggedNotices.has(notice.message)) return null;
loggedNotices.add(notice.message);
return notice;
}
function formatSlideAnnouncement(options) {
const base = options.template.replace("{index}", String(options.index)).replace("{total}", String(options.total));
const caption = (options.caption || "").replace(/\s+/g, " ").trim();
return caption ? `${base}, ${caption}` : base;
}
const SPRING_SETTLE_DAMPING = 1;
const SPRING_NATURAL_FREQUENCY = 12;
const DECELERATION_RATE = 0.995;
function project(velocity, decelerationRate = DECELERATION_RATE) {
return velocity * decelerationRate / (1 - decelerationRate);
}
function stepSpring(state, target, dtMs, {
dampingRatio = SPRING_SETTLE_DAMPING,
naturalFrequency = SPRING_NATURAL_FREQUENCY
} = {}) {
const t = dtMs / 1e3;
const w0 = naturalFrequency;
const zeta = Math.min(dampingRatio, 1);
const x0 = state.position - target;
const v0 = state.velocity * 1e3;
const decay = Math.exp(-zeta * w0 * t);
let x;
let v;
if (zeta < 1) {
const wd = w0 * Math.sqrt(1 - zeta * zeta);
const a = x0;
const b = (v0 + zeta * w0 * x0) / wd;
const cos = Math.cos(wd * t);
const sin = Math.sin(wd * t);
x = decay * (a * cos + b * sin);
v = decay * ((b * wd - zeta * w0 * a) * cos - (a * wd + zeta * w0 * b) * sin);
} else {
const b = v0 + w0 * x0;
x = decay * (x0 + b * t);
v = decay * (b - w0 * (x0 + b * t));
}
return { position: target + x, velocity: v / 1e3 };
}
function isSpringSettled(state, target, restDelta = 0.3, restVelocity = 0.012) {
return Math.abs(state.position - target) < restDelta && Math.abs(state.velocity) < restVelocity;
}
const SWIPE_AXIS_THRESHOLD = 10;
const FLICK_VELOCITY = 0.5;
const FLICK_MIN_DISTANCE = 20;
const VERTICAL_CLOSE_THRESHOLD = 100;
const VERTICAL_CLOSE_RATIO = 0.4;
const VERTICAL_CLOSE_MIN_DRAG = 40;
const SLIDE_EDGE_FRICTION = 0.35;
function getEdgeFrictionedDelta(deltaX, hasPrev, hasNext, direction = "ltr") {
const [towardPrev, towardNext] = direction === "rtl" ? [hasNext, hasPrev] : [hasPrev, hasNext];
if (deltaX > 0 && !towardPrev || deltaX < 0 && !towardNext) {
return deltaX * SLIDE_EDGE_FRICTION;
}
return deltaX;
}
function getSwipeAxis(deltaX, deltaY, current) {
if (current) {
return current;
}
if (Math.abs(deltaX) > SWIPE_AXIS_THRESHOLD) {
return "horizontal";
}
if (Math.abs(deltaY) > SWIPE_AXIS_THRESHOLD) {
return "vertical";
}
return void 0;
}
function getHorizontalDragTransforms(deltaX, slideWidth, direction = "ltr") {
const slideWidthAmount = slideWidth * 15 / 100;
const gutter = slideWidthAmount - Math.abs(deltaX * 10 / 100);
const before = `translate3d(${-slideWidth + deltaX - gutter}px, 0px, 0px)`;
const after = `translate3d(${slideWidth + deltaX + gutter}px, 0px, 0px)`;
return {
current: `translate3d(${deltaX}px, 0px, 0px)`,
// In RTL the prev slide rests to the physical right (lg-rtl.css
// flips the resting positions the same way).
prev: direction === "rtl" ? after : before,
next: direction === "rtl" ? before : after
};
}
function getVerticalDragEffects(deltaY, viewportWidth, viewportHeight) {
const distance = Math.abs(deltaY);
const scale = 1 - distance / (viewportWidth * 2);
return {
backdropOpacity: 1 - distance / viewportHeight,
transform: `translate3d(0px, ${deltaY}px, 0px) scale3d(${scale}, ${scale}, 1)`,
hideUi: distance > VERTICAL_CLOSE_THRESHOLD
};
}
function getSwipeReleaseVerdict({
deltaX,
velocityX,
threshold,
flickVelocity = FLICK_VELOCITY,
viewportWidth,
direction = "ltr"
}) {
const distance = Math.abs(deltaX);
const directionMatches = deltaX !== 0 && Math.sign(velocityX) === Math.sign(deltaX);
const projected = deltaX + project(velocityX);
const passes = distance > threshold || distance > FLICK_MIN_DISTANCE && directionMatches && Math.abs(velocityX) > flickVelocity || viewportWidth !== void 0 && distance > FLICK_MIN_DISTANCE && directionMatches && Math.abs(projected) > viewportWidth / 2;
if (!passes) {
return "stay";
}
const towardNext = direction === "rtl" ? deltaX > 0 : deltaX < 0;
return towardNext ? "next" : "prev";
}
function shouldCloseOnVerticalDrag(deltaY, velocityY, viewportHeight, options) {
if (!options.closable || !options.swipeToClose || deltaY === 0) {
return false;
}
if (Math.abs(deltaY) < VERTICAL_CLOSE_MIN_DRAG) {
return false;
}
const projected = deltaY + project(velocityY);
return Math.sign(projected) === Math.sign(deltaY) && Math.abs(projected) > VERTICAL_CLOSE_RATIO * viewportHeight;
}
function resolveSwipeTarget(verdict, currentIndex, slidesCount, loop) {
if (verdict === "stay") {
return null;
}
const touchLoop = loop && slidesCount >= 3;
if (verdict === "next") {
if (currentIndex + 1 < slidesCount) {
return currentIndex + 1;
}
return touchLoop ? 0 : null;
}
if (currentIndex > 0) {
return currentIndex - 1;
}
return touchLoop ? slidesCount - 1 : null;
}
function parseImageSize(lgSize, viewportWidth) {
if (!lgSize) {
return void 0;
}
let size = lgSize;
const responsiveSizes = lgSize.split(",");
if (responsiveSizes[1]) {
for (let i = 0; i < responsiveSizes.length; i++) {
const candidate = responsiveSizes[i].trim();
const responsiveWidth = parseInt(candidate.split("-")[2], 10);
if (responsiveWidth > viewportWidth) {
size = candidate;
break;
}
if (i === responsiveSizes.length - 1) {
size = candidate;
}
}
}
const parts = size.trim().split("-");
const width = parseInt(parts[0], 10);
const height = parseInt(parts[1], 10);
if (!Number.isFinite(width) || !Number.isFinite(height)) {
return void 0;
}
return { width, height };
}
function fitImageSize(size, containerWidth, containerHeight) {
const maxWidth = Math.min(containerWidth, size.width);
const maxHeight = Math.min(containerHeight, size.height);
const ratio = Math.min(maxWidth / size.width, maxHeight / size.height);
return { width: size.width * ratio, height: size.height * ratio };
}
function isUsableOriginRect(rect) {
return !!rect && Number.isFinite(rect.left) && Number.isFinite(rect.top) && rect.width > 0 && rect.height > 0;
}
const CENTER_CLOSE_SCALE = 0.5;
function getCenterCloseTransform(scale = CENTER_CLOSE_SCALE) {
return `translate3d(0, 0, 0) scale3d(${scale}, ${scale}, 1)`;
}
function getOriginTransform(input) {
const { triggerRect, containerRect, top, bottom, imageSize } = input;
const availableWidth = containerRect.width;
const availableHeight = containerRect.height - (top + bottom);
const x = (availableWidth - triggerRect.width) / 2 - triggerRect.left + containerRect.left;
const y = (availableHeight - triggerRect.height) / 2 - triggerRect.top + top;
const scaleX = triggerRect.width / imageSize.width;
const scaleY = triggerRect.height / imageSize.height;
return `translate3d(${-x}px, ${-y}px, 0) scale3d(${scaleX}, ${scaleY}, 1)`;
}
const coreDefaultIcons = {
close: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="currentColor"><path transform="translate(0, 960) scale(1, -1)" d="M810 664.667l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z"/></svg>',
// Three dots for the toolbar's More options menu button.
more: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="currentColor"><circle cx="232" cy="512" r="80"/><circle cx="512" cy="512" r="80"/><circle cx="792" cy="512" r="80"/></svg>',
prev: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="currentColor"><path transform="translate(0, 960) scale(1, -1)" d="M426.667 768q17.667 0 30.167-12.5t12.5-30.167q0-18-12.667-30.333l-225.667-225.667h665q17.667 0 30.167-12.5t12.5-30.167-12.5-30.167-30.167-12.5h-665l225.667-225.667q12.667-12.333 12.667-30.333 0-17.667-12.5-30.167t-30.167-12.5q-18 0-30.333 12.333l-298.667 298.667q-12.333 13-12.333 30.333t12.333 30.333l298.667 298.667q12.667 12.333 30.333 12.333z"/></svg>',
next: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="currentColor"><path transform="translate(0, 960) scale(1, -1)" d="M597.333 768q18 0 30.333-12.333l298.667-298.667q12.333-12.333 12.333-30.333t-12.333-30.333l-298.667-298.667q-12.333-12.333-30.333-12.333-18.333 0-30.5 12.167t-12.167 30.5q0 18 12.333 30.333l226 225.667h-665q-17.667 0-30.167 12.5t-12.5 30.167 12.5 30.167 30.167 12.5h665l-226 225.667q-12.333 12.333-12.333 30.333 0 18.333 12.167 30.5t30.5 12.167z"/></svg>',
download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="currentColor"><path transform="translate(0, 960) scale(1, -1)" d="M170 128.667h684v-86h-684v86zM682 384.667l-170-172-170 172h128v426h84v-426h128z"/></svg>',
maximize: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="currentColor"><path transform="translate(0, 960) scale(1, -1)" d="M793.003 768l-225.835-225.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l225.835 225.835v-153.003c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 5.803-1.152 11.307-3.243 16.341s-5.163 9.728-9.216 13.781c-0.043 0.043-0.043 0.043-0.085 0.085-3.925 3.925-8.619 7.083-13.781 9.216-5.035 2.091-10.539 3.243-16.341 3.243h-256c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667zM230.997 85.334l225.835 225.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-225.835-225.835v153.003c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-256c0-23.552 19.115-42.667 42.667-42.667h256c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667z"/></svg>',
minimize: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="currentColor"><path transform="translate(0, 960) scale(1, -1)" d="M700.331 554.667l225.835 225.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-225.835-225.835v153.003c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-256c0-5.803 1.152-11.307 3.243-16.341s5.163-9.728 9.216-13.781c0.043-0.043 0.043-0.043 0.085-0.085 3.925-3.925 8.619-7.083 13.781-9.216 5.035-2.091 10.539-3.243 16.341-3.243h256c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM158.165 12.502l225.835 225.835v-153.003c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 5.803-1.152 11.307-3.243 16.341s-5.163 9.728-9.216 13.781c-0.043 0.043-0.043 0.043-0.085 0.085-4.096 4.053-8.789 7.125-13.781 9.216-5.035 2.091-10.539 3.243-16.341 3.243h-256c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h153.003l-225.835-225.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0z"/></svg>'
};
var __defProp$1 = Object.defineProperty;
var __defProps$1 = Object.defineProperties;
var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues$1 = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp$1.call(b, prop))
__defNormalProp$1(a, prop, b[prop]);
if (__getOwnPropSymbols$1)
for (var prop of __getOwnPropSymbols$1(b)) {
if (__propIsEnum$1.call(b, prop))
__defNormalProp$1(a, prop, b[prop]);
}
return a;
};
var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b));
const TOOLBAR_PINNED = Number.POSITIVE_INFINITY;
const TOOLBAR_DEFAULT_PRIORITY = 20;
const PRIORITY_BY_CLASS = [
["lg-close", TOOLBAR_PINNED],
["lg-share", 30],
["lg-autoplay-button", 30],
["lg-download", 30],
["lg-fullscreen", 20],
["lg-toggle-thumb", 20],
["lg-comment-toggle", 20],
["lg-maximize", 10],
["lg-rotate-left", 10],
["lg-rotate-right", 10],
["lg-flip-hor", 10],
["lg-flip-ver", 10],
// Gesture duplicates go first: pinch and double-tap do the same. The
// actual-size button carries one of these two classes as well.
["lg-zoom-in", 0],
["lg-zoom-out", 0]
];
function getToolbarItemPriority(classList) {
const classes = new Set(classList);
for (const [className, priority] of PRIORITY_BY_CLASS) {
if (classes.has(className)) {
return priority;
}
}
return TOOLBAR_DEFAULT_PRIORITY;
}
function getToolbarOverflow({
available,
reserved,
moreWidth,
items
}) {
if (available <= 0) {
return [];
}
const total = items.reduce((sum, item) => sum + item.width, 0);
if (reserved + total <= available) {
return [];
}
const candidates = items.map((item, index) => __spreadProps$1(__spreadValues$1({}, item), { index })).filter((item) => item.priority !== TOOLBAR_PINNED).sort((a, b) => a.priority - b.priority || b.index - a.index);
let used = reserved + moreWidth + total;
const moved = [];
for (const item of candidates) {
if (used <= available) {
break;
}
moved.push(item.index);
used -= item.width;
}
return moved.sort((a, b) => a - b);
}
const BACKDROP_CLASS_NAMES = [
"lg-outer",
"lg-item",
"lg-img-wrap",
"lg-img-rotate"
];
function consumeBackdropPress(event) {
var _a;
const classList = (_a = event.target) == null ? void 0 : _a.classList;
if (!classList || !BACKDROP_CLASS_NAMES.some((name) => classList.contains(name))) {
return false;
}
event.preventDefault();
event.stopPropagation();
return true;
}
function onTransitionSettle(element, property, fallbackMs, callback) {
let settled = false;
let timer;
const isOwn = (event) => event.target === element && event.propertyName === property;
const dispose = () => {
clearTimeout(timer);
element.removeEventListener("transitionstart", onStart);
element.removeEventListener("transitionend", onEnd);
};
const settle = () => {
if (settled) {
return;
}
settled = true;
dispose();
callback();
};
const onStart = (event) => {
if (isOwn(event)) {
clearTimeout(timer);
timer = setTimeout(settle, fallbackMs);
}
};
const onEnd = (event) => {
if (isOwn(event)) {
settle();
}
};
element.addEventListener("transitionstart", onStart);
element.addEventListener("transitionend", onEnd);
timer = setTimeout(settle, fallbackMs);
return () => {
settled = true;
dispose();
};
}
const DECODE_TIMEOUT_MS = 500;
function awaitDecode(img, timeoutMs = DECODE_TIMEOUT_MS) {
if (typeof img.decode !== "function") {
return Promise.resolve();
}
return new Promise((resolve) => {
let done = false;
const finish = () => {
if (!done) {
done = true;
clearTimeout(timer);
resolve();
}
};
const timer = setTimeout(finish, timeoutMs);
img.decode().then(finish, finish);
});
}
const VELOCITY_WINDOW_MS = 100;
const MIN_DISPLACEMENT = 1;
const MIN_WINDOW_SPAN_MS = 5;
function pushVelocitySample(samples, sample, windowMs = VELOCITY_WINDOW_MS) {
return [...samples.filter((s) => sample.t - s.t <= windowMs), sample];
}
function getWindowedVelocity(samples, releaseTime, windowMs = VELOCITY_WINDOW_MS) {
const recent = samples.filter((s) => releaseTime - s.t <= windowMs);
if (recent.length < 2) {
return { x: 0, y: 0 };
}
const first = recent[0];
const last = recent[recent.length - 1];
const dt = last.t - first.t;
if (dt < MIN_WINDOW_SPAN_MS) {
return { x: 0, y: 0 };
}
const dx = last.x - first.x;
const dy = last.y - first.y;
return {
x: Math.abs(dx) > MIN_DISPLACEMENT ? dx / dt : 0,
y: Math.abs(dy) > MIN_DISPLACEMENT ? dy / dt : 0
};
}
const lGEvents = {
afterAppendSlide: "lgAfterAppendSlide",
init: "lgInit",
hasVideo: "lgHasVideo",
containerResize: "lgContainerResize",
updateSlides: "lgUpdateSlides",
afterAppendSubHtml: "lgAfterAppendSubHtml",
beforeOpen: "lgBeforeOpen",
afterOpen: "lgAfterOpen",
slideItemLoad: "lgSlideItemLoad",
beforeSlide: "lgBeforeSlide",
afterSlide: "lgAfterSlide",
posterClick: "lgPosterClick",
dragStart: "lgDragStart",
dragMove: "lgDragMove",
dragEnd: "lgDragEnd",
beforeNextSlide: "lgBeforeNextSlide",
beforePrevSlide: "lgBeforePrevSlide",
beforeClose: "lgBeforeClose",
afterClose: "lgAfterClose"
};
const lightGalleryCoreSettings = {
mode: "lg-slide",
easing: "ease",
speed: 400,
licenseKey: "0000-0000-000-0000",
height: "100%",
width: "100%",
addClass: "",
startClass: "lg-start-zoom",
backdropDuration: 300,
container: "",
startAnimationDuration: 400,
zoomFromOrigin: true,
hideBarsDelay: 0,
showBarsAfter: 1e4,
slideDelay: 0,
allowMediaOverlap: false,
videoMaxSize: "1280-720",
loadYouTubePoster: true,
defaultCaptionHeight: 0,
ariaLabelledby: "",
ariaDescribedby: "",
ariaAnnouncements: true,
resetScrollPosition: true,
hideScrollbar: false,
closable: true,
swipeToClose: true,
closeOnTap: true,
showCloseIcon: true,
showMaximizeIcon: false,
loop: true,
escKey: true,
keyPress: true,
trapFocus: true,
controls: true,
slideEndAnimation: true,
hideControlOnEnd: false,
mousewheel: false,
direction: "ltr",
getCaptionFromTitleOrAlt: true,
appendSubHtmlTo: ".lg-sub-html",
subHtmlSelectorRelative: false,
preload: 2,
numberOfSlideItemsInDom: 10,
selector: "",
selectWithin: "",
nextHtml: "",
prevHtml: "",
index: 0,
iframeWidth: "100%",
iframeHeight: "100%",
iframeMaxWidth: "100%",
iframeMaxHeight: "100%",
download: true,
counter: true,
appendCounterTo: ".lg-toolbar",
swipeThreshold: 50,
flickVelocity: 0.5,
pinchToClose: true,
enableSwipe: true,
enableDrag: true,
dynamic: false,
dynamicEl: [],
extraProps: [],
exThumbImage: "",
toolbarOverflow: true,
showGestureButtons: true,
isMobile: void 0,
mobileSettings: {
controls: false,
showCloseIcon: false,
download: false,
showGestureButtons: false
},
plugins: [],
strings: {
closeGallery: "Close gallery",
toggleMaximize: "Toggle maximize",
previousSlide: "Previous slide",
nextSlide: "Next slide",
download: "Download",
playVideo: "Play video",
mediaLoadingFailed: "Oops... Failed to load content...",
galleryLabel: "Gallery",
slideAnnouncement: "Image {index} of {total}",
moreOptions: "More options",
share: "Share",
toggleThumbnails: "Toggle thumbnails",
toggleAutoplay: "Toggle Autoplay",
toggleFullscreen: "Toggle Fullscreen",
zoomIn: "Zoom in",
zoomOut: "Zoom out",
viewActualSize: "View actual size",
rotateLeft: "Rotate left",
rotateRight: "Rotate right",
flipHorizontal: "Flip horizontal",
flipVertical: "Flip vertical",
toggleComments: "Toggle Comments"
},
icons: {}
};
const schedule = typeof requestAnimationFrame === "function" ? (callback) => requestAnimationFrame(callback) : (callback) => window.setTimeout(callback, 16);
const cancel = typeof cancelAnimationFrame === "function" ? (handle) => cancelAnimationFrame(handle) : (handle) => window.clearTimeout(handle);
function toolbarButtons(toolbar, more) {
return Array.from(toolbar.querySelectorAll(".lg-icon")).filter(
(button) => {
var _a;
return button !== more && !button.closest(".lg-toolbar-menu, .lg-dropdown") && !((_a = button.parentElement) == null ? void 0 : _a.closest(".lg-icon"));
}
);
}
function visibleIconMarkup(button) {
var _a, _b, _c;
const icons = Array.from(button.querySelectorAll(".lg-ci"));
if (!icons.length) {
return (_b = (_a = button.querySelector("svg")) == null ? void 0 : _a.outerHTML) != null ? _b : "";
}
const shown = (_c = icons.find(
(icon) => window.getComputedStyle(icon).display !== "none"
)) != null ? _c : icons[0];
return shown.outerHTML;
}
class ToolbarOverflow {
constructor(toolbar, options) {
this.toolbar = toolbar;
this.options = options;
this.menu = null;
this.frame = 0;
this.movedSignature = "";
this.widths = /* @__PURE__ */ new WeakMap();
this.requestUpdate = () => {
if (this.frame) {
return;
}
this.frame = schedule(() => {
this.frame = 0;
this.update();
});
};
this.onMoreClick = () => {
if (this.menu) {
this.close(true);
} else {
this.open();
}
};
this.onMenuKeydown = (event) => {
var _a, _b, _c, _d;
const items = this.items();
const index = items.indexOf(document.activeElement);
switch (event.key) {
case "Escape":
this.close(true);
break;
case "ArrowDown":
(_a = items[(index + 1) % items.length]) == null ? void 0 : _a.focus();
break;
case "ArrowUp":
(_b = items[(index - 1 + items.length) % items.length]) == null ? void 0 : _b.focus();
break;
case "Home":
(_c = items[0]) == null ? void 0 : _c.focus();
break;
case "End":
(_d = items[items.length - 1]) == null ? void 0 : _d.focus();
break;
case "ArrowLeft":
case "ArrowRight":
break;
case "Tab":
this.close(false);
return;
default:
return;
}
event.preventDefault();
event.stopPropagation();
};
this.onDocumentPointerDown = (event) => {
var _a;
const target = event.target;
if (target && (((_a = this.menu) == null ? void 0 : _a.contains(target)) || this.more.contains(target))) {
return;
}
consumeBackdropPress(event);
this.close(false);
};
const more = document.createElement("button");
more.type = "button";
more.id = options.id;
more.className = "lg-more lg-icon";
more.hidden = true;
more.setAttribute("aria-label", options.label);
more.setAttribute("aria-haspopup", "menu");
more.setAttribute("aria-expanded", "false");
more.addEventListener("click", this.onMoreClick);
const close = toolbar.querySelector(".lg-close");
if (close) {
close.insertAdjacentElement("afterend", more);
} else {
toolbar.insertBefore(more, toolbar.firstChild);
}
this.more = more;
if (typeof ResizeObserver === "function") {
this.resizeObserver = new ResizeObserver(this.requestUpdate);
this.resizeObserver.observe(toolbar);
}
if (typeof MutationObserver === "function") {
this.mutationObserver = new MutationObserver((records) => {
if (records.some(
(record) => {
var _a, _b;
return !((_b = (_a = record.target).closest) == null ? void 0 : _b.call(
_a,
".lg-toolbar-menu"
));
}
)) {
this.requestUpdate();
}
});
this.mutationObserver.observe(toolbar, {
childList: true,
subtree: true
});
}
}
/** Re-measure the row and move buttons in or out of the menu. */
update() {
var _a, _b;
const buttons = toolbarButtons(this.toolbar, this.more);
buttons.forEach((button) => {
if (!button.hasAttribute("data-lg-overflow")) {
const width = button.offsetWidth;
if (width) {
this.widths.set(button, width);
}
}
});
const fallback = (_a = buttons.map((button) => this.widths.get(button)).find((width) => !!width)) != null ? _a : 0;
const counter = this.toolbar.querySelector(".lg-counter");
const moved = getToolbarOverflow({
available: this.toolbar.clientWidth,
reserved: counter ? counter.offsetWidth : 0,
moreWidth: (_b = this.widths.get(this.more)) != null ? _b : fallback,
items: buttons.map((button) => {
var _a2;
return {
width: (_a2 = this.widths.get(button)) != null ? _a2 : fallback,
priority: getToolbarItemPriority(Array.from(button.classList))
};
})
});
const overflow = new Set(moved);
buttons.forEach((button, index) => {
if (overflow.has(index)) {
button.setAttribute("data-lg-overflow", "");
} else {
button.removeAttribute("data-lg-overflow");
}
});
this.more.hidden = overflow.size === 0;
if (!this.more.hidden && this.more.offsetWidth) {
this.widths.set(this.more, this.more.offsetWidth);
}
const signature = `${buttons.length}:${moved.join(",")}`;
const changed = signature !== this.movedSignature;
this.movedSignature = signature;
if (!overflow.size) {
this.close(false);
} else if (this.menu && changed) {
this.renderMenu();
}
}
/** Close the menu; optionally hand focus back to the More button. */
close(returnFocus) {
if (!this.menu) {
return;
}
this.menu.removeEventListener("keydown", this.onMenuKeydown);
this.menu.remove();
this.menu = null;
document.removeEventListener(
"pointerdown",
this.onDocumentPointerDown,
true
);
this.more.setAttribute("aria-expanded", "false");
this.more.removeAttribute("aria-controls");
if (returnFocus) {
this.more.focus();
}
}
destroy() {
var _a, _b;
this.close(false);
cancel(this.frame);
(_a = this.resizeObserver) == null ? void 0 : _a.disconnect();
(_b = this.mutationObserver) == null ? void 0 : _b.disconnect();
toolbarButtons(this.toolbar, this.more).forEach(
(button) => button.removeAttribute("data-lg-overflow")
);
this.more.remove();
}
open() {
var _a;
const menu = document.createElement("div");
menu.id = `${this.options.id}-menu`;
menu.className = "lg-toolbar-menu";
menu.setAttribute("role", "menu");
menu.setAttribute("aria-label", this.options.label);
menu.addEventListener("keydown", this.onMenuKeydown);
this.menu = menu;
this.renderMenu();
this.toolbar.appendChild(menu);
this.more.setAttribute("aria-expanded", "true");
this.more.setAttribute("aria-controls", menu.id);
document.addEventListener(
"pointerdown",
this.onDocumentPointerDown,
true
);
(_a = this.items()[0]) == null ? void 0 : _a.focus();
}
renderMenu() {
const menu = this.menu;
if (!menu) {
return;
}
menu.textContent = "";
toolbarButtons(this.toolbar, this.more).filter((button) => button.hasAttribute("data-lg-overflow")).forEach((button) => {
var _a;
const item = document.createElement("button");
item.type = "button";
item.className = "lg-toolbar-menu-item";
item.setAttribute("role", "menuitem");
const icon = document.createElement("span");
icon.className = "lg-toolbar-menu-icon";
icon.setAttribute("aria-hidden", "true");
icon.innerHTML = visibleIconMarkup(button);
const label = document.createElement("span");
label.textContent = (_a = button.getAttribute("aria-label")) != null ? _a : "";
item.append(icon, label);
item.addEventListener("click", () => {
this.close(true);
button.click();
});
menu.appendChild(item);
});
}
items() {
return this.menu ? Array.from(
this.menu.querySelectorAll('[role="menuitem"]')
) : [];
}
}
const ICON_TARGETS = [
{ selector: ".lg-close", names: ["close"] },
{ selector: ".lg-prev", names: ["prev"] },
{ selector: ".lg-next", names: ["next"] },
{ selector: ".lg-download", names: ["download"] },
{ selector: ".lg-more", names: ["more"] },
// Shown while `.lg-inline` (prompting maximize) / after maximizing.
{ selector: ".lg-maximize", names: ["maximize", "minimize"] },
// The actual-size button's CLASS toggles between the two zoom
// classes as the zoom state changes (2.x swapped the glyph that
// way), it carries BOTH zoom icons and the class picks one in
// CSS. Listed before the class-based zoom targets so the id match
// claims it first (the pass skips already-processed elements).
{ selector: '[id^="lg-actual-size-"]', names: ["zoomIn", "zoomOut"] },
{ selector: ".lg-zoom-in", names: ["zoomIn"] },
{ selector: ".lg-zoom-out", names: ["zoomOut"] },
// No '.lg-actual-size' target: vanilla's actual-size BUTTON never
// carries that class (it rides the zoom classes above; the outer
// gets `lg-actual-size` as a zoom-state class after build). The
// `actualSize` name serves the framework ports' static button.
{ selector: ".lg-rotate-left", names: ["rotateLeft"] },
{ selector: ".lg-rotate-right", names: ["rotateRight"] },
{ selector: ".lg-flip-hor", names: ["flipHorizontal"] },
{ selector: ".lg-flip-ver", names: ["flipVertical"] },
{ selector: ".lg-share", names: ["share"] },
{ selector: ".lg-share-facebook .lg-icon", names: ["shareFacebook"] },
{ selector: ".lg-share-twitter .lg-icon", names: ["shareX"] },
{ selector: ".lg-share-pinterest .lg-icon", names: ["sharePinterest"] },
// Shown while idle (prompting play) / while `.lg-show-autoplay`.
{
selector: ".lg-autoplay-button",
names: ["autoplayPlay", "autoplayPause"]
},
// Shown while windowed / while `.lg-fullscreen-on`.
{ selector: ".lg-fullscreen", names: ["fullscreen", "fullscreenExit"] },
{ selector: ".lg-comment-toggle", names: ["comment"] },
{ selector: ".lg-comment-close", names: ["commentClose"] },
{ selector: ".lg-toggle-thumb", names: ["toggleThumbnails"] }
];
const toKebab = (name) => name.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
function getCustomIconMarkup(icons, names) {
if (!names.every((name) => icons[name])) {
return "";
}
return names.map(
(name) => `<span class="lg-ci lg-ci-${toKebab(
name
)}" aria-hidden="true">${icons[name]}</span>`
).join("");
}
function applyCustomIcons(container, defaults, overrides) {
ICON_TARGETS.forEach(({ selector, names }) => {
const source = overrides && names.every((name) => overrides[name]) ? overrides : defaults;
const markup = getCustomIconMarkup(source, names);
if (!markup) {
return;
}
container.querySelectorAll(selector).forEach((element) => {
if (element.classList.contains("lg-icon-custom")) {
return;
}
element.classList.add("lg-icon-custom");
element.insertAdjacentHTML("afterbegin", markup);
});
});
}
const MAX_FRAME_MS = 64;
function runSprings(tracks, onFrame, onDone) {
let raf = 0;
let last = Date.now();
const states = tracks.map((t) => ({
position: t.from,
velocity: t.velocity
}));
const frame = () => {
const now = Date.now();
const dt = Math.min(Math.max(now - last, 0), MAX_FRAME_MS);
last = now;
let settled = true;
tracks.forEach((t, i) => {
states[i] = stepSpring(states[i], t.target, dt, t);
if (!isSpringSettled(states[i], t.target)) {
settled = false;
}
});
if (settled) {
onFrame(tracks.map((t) => t.target));
onDone == null ? void 0 : onDone();
return;
}
onFrame(states.map((s) => s.position));
raf = requestAnimationFrame(frame);
};
raf = requestAnimationFrame(frame);
return () => cancelAnimationFrame(raf);
}
const _lgQuery = class _lgQuery2 {
constructor(selector) {
this.cssVenderPrefixes = [
"TransitionDuration",
"TransitionTimingFunction",
"Transform",
"Transition"
];
this.selector = this._getSelector(selector);
this.firstElement = this._getFirstEl();
return this;
}
static generateUUID() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function(c) {
const r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8;
return v.toString(16);
}
);
}
_getSelector(selector, context = document) {
if (typeof selector !== "string") {
return selector;
}
context = context || document;
const fl = selector.substring(0, 1);
if (fl === "#") {
return context.querySelector(selector);
} else {
return context.querySelectorAll(selector);
}
}
_each(func) {
if (!this.selector) {
return this;
}
if (this.selector.length !== void 0) {
[].forEach.call(this.selector, func);
} else {
func(this.selector, 0);
}
return this;
}
_setCssVendorPrefix(el, cssProperty, value) {
if (cssProperty.startsWith("--")) {
el.style.setProperty(cssProperty, String(value));
return;
}
const property = cssProperty.replace(/-([a-z])/gi, function(s, group1) {
return group1.toUpperCase();
});
if (this.cssVenderPrefixes.indexOf(property) !== -1) {
el.style[property.charAt(0).toLowerCase() + property.slice(1)] = value;
el.style["webkit" + property] = value;
el.style["moz" + property] = value;
el.style["ms" + property] = value;
el.style["o" + property] = value;
} else {
el.style[property] = value;
}
}
_getFirstEl() {
if (this.selector && this.selector.length !== void 0) {
return this.selector[0];
} else {
return this.selector;
}
}
isEventMatched(event, eventName) {
const eventNamespace = eventName.split(".");
return event.split(".").filter((e) => e).every((e) => {
return eventNamespace.indexOf(e) !== -1;
});
}
attr(attr, value) {
if (value === void 0) {
if (!this.firstElement) {
return "";
}
return this.firstElement.getAttribute(attr);
}
this._each((el) => {
el.setAttribute(attr, value);
});
return this;
}
find(selector) {
return $LG(this._getSelector(selector, this.selector));
}
first() {
if (this.selector && this.selector.length !== void 0) {
return $LG(this.selector[0]);
} else {
return $LG(this.selector);
}
}
eq(index) {
return $LG(this.selector[index]);
}
parent() {
return $LG(this.selector.parentElement);
}
get() {
return this._getFirstEl();
}
removeAttr(attributes) {
const attrs = attributes.split(" ");
this._each((el) => {
attrs.forEach((attr) => el.removeAttribute(attr));
});
return this;
}
wrap(className) {
if (!this.firstElement) {
return this;
}
const wrapper = document.createElement("div");
wrapper.className = className;
this.firstElement.parentNode.insertBefore(wrapper, this.firstElement);
this.firstElement.parentNode.removeChild(this.firstElement);
wrapper.appendChild(this.firstElement);
return this;
}
addClass(classNames = "") {
this._each((el) => {
classNames.split(" ").forEach((className) => {
if (className) {
el.classList.add(className);
}
});
});
return this;
}
removeClass(classNames) {
this._each((el) => {
classNames.split(" ").forEach((className) => {
if (className) {
el.classList.remove(className);
}
});
});
return this;
}
hasClass(className) {
if (!this.firstElement) {
return false;
}
return this.firstElement.classList.contains(className);
}
hasAttribute(attribute) {
if (!this.firstElement) {
return false;
}
return this.firstElement.hasAttribute(attribute);
}
toggleClass(className) {
if (!this.firstElement) {
return this;
}
if (this.hasClass(className)) {
this.removeClass(className);
} else {
this.addClass(className);
}
return this;
}
css(property, value) {
this._each((el) => {
this._setCssVendorPrefix(el, property, value);
});
return this;
}
// Need to pass separate namespaces for separate elements
on(events, listener) {
if (!this.selector) {
return this;
}
events.split(" ").forEach((event) => {
if (!Array.isArray(_lgQuery2.eventListeners[event])) {
_lgQuery2.eventListeners[event] = [];
}
_lgQuery2.eventListeners[event].push(listener);
this.selector.addEventListener(event.split(".")[0], listener);
});
return this;
}
// @todo - test this
once(event, listener) {
this.on(event, () => {
this.off(event);
listener(event);
});
return this;
}
off(event) {
if (!this.selector) {
return this;
}
Object.keys(_lgQuery2.eventListeners).forEach((eventName) => {
if (this.isEventMatched(event, eventName)) {
_lgQuery2.eventListeners[eventName].forEach((listener) => {
this.selector.removeEventListener(
eventName.split(".")[0],
listener
);
});
_lgQuery2.eventListeners[eventName] = [];
}
});
return this;
}
trigger(event, detail) {
if (!this.firstElement) {
return this;
}
const customEvent = new CustomEvent(event.split(".")[0], {
detail: detail || null
});
this.firstElement.dispatchEvent(customEvent);
return this;
}
// Does not support IE
load(url) {
fetch(url).then((res) => res.text()).then((html) => {
this.selector.innerHTML = html;
});
return this;
}
html(html) {
if (html === void 0) {
if (!this.firstElement) {
return "";
}
return this.firstElement.innerHTML;
}
this._each((el) => {
el.innerHTML = html;
});
return this;
}
append(html) {
this._each((el) => {
if (typeof html === "string") {
el.insertAdjacentHTML("beforeend", html);
} else {
el.appendChild(html);
}
});
return this;
}
prepend(html) {
this._each((el) => {
if (typeof html === "string") {
el.insertAdjacentHTML("afterbegin", html);
} else if (html instanceof HTMLElement) {
el.insertBefore(html.cloneNode(true), el.firstChild);
}
});
return this;
}
remove() {
this._each((el) => {
el.parentNode.removeChild(el);
});
return this;
}
empty() {
this._each((el) => {
el.innerHTML = "";
});
return this;
}
scrollTop(scrollTop) {
if (scrollTop !== void 0) {
window.scrollTo(window.scrollX, scrollTop);
return this;
} else {
return window.scrollY;
}
}
scrollLeft(scrollLeft) {
if (scrollLeft !== void 0) {
window.scrollTo(scrollLeft, window.scrollY);
return this;
} else {
return window.scrollX;
}
}
offset() {
if (!this.firstElement) {
return {
left: 0,
top: 0
};
}
const rect = this.firstElement.getBoundingClientRect();
const bodyMarginLeft = $LG("body").style().marginLeft;
return {
left: rect.left - parseFloat(bodyMarginLeft) + this.scrollLeft(),
top: rect.top + this.scrollTop()
};
}
style() {
if (!this.firstElement) {
return {};
}
return this.firstElement.currentStyle || window.getComputedStyle(this.firstElement);
}
// Width without padding and border even if box-sizing is used.
width() {
const style = this.style();
return this.firstElement.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
}
// Height without padding and border even if box-sizing is used.
height() {
const style = this.style();
return this.firstElement.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);
}
};
_lgQuery.eventListeners = {};
let lgQuery = _lgQuery;
function $LG(selector) {
return new lgQuery(selector);
}
const defaultDynamicOptions = [
"src",
"sources",
"subHtml",
"subHtmlUrl",
"html",
"video",
"poster",
"slideName",
"responsive",
"srcset",
"sizes",
"iframe",
"downloadUrl",
"download",
"width",
"shareUrl",
"facebookShareUrl",
"tweetText",
"iframeTitle",
"twitterShareUrl",
"pinterestShareUrl",
"pinterestText",
"fbHtml",
"disqusIdentifier",
"disqusUrl",
"lgSize"
];
function convertToData(attr) {
if (attr === "href") {
return "src";
}
attr = attr.replace("data-", "");
attr = attr.charAt(0).toLowerCase() + attr.slice(1);
attr = attr.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
return attr;
}
const utils = {
/**
* Fetches HTML content from a given URL and inserts it into a specified element.
*
* @param url - The URL to fetch the HTML content from.
* @param element - The DOM element (jQuery object) to insert the HTML content into.
* @param insertMethod - The method to insert the HTML ('append' or 'replace').
*/
fetchCaptionFromUrl(url, element, insertMethod) {
fetch(url).then((response) => response.text()).then((htmlContent) => {
if (insertMethod === "append") {
const contentDiv = `<div class="lg-sub-html">${htmlContent}</div>`;
element.append(contentDiv);
} else {
element.html(htmlContent);
}
});
},
/**
* Fit the media's original size into the container. The size comes from
* the element's `data-lg-size` attribute, then the item's `lgSize`
* field, then `defaultLgSize`. Used for the zoom-from-origin animation.
*/
getSize(el, container, spacing = 0, defaultLgSize, itemLgSize) {
const LGel = $LG(el);
const lgSize = LGel.attr("data-lg-size") || itemLgSize || defaultLgSize;
const parsed = parseImageSize(lgSize, window.innerWidth);
if (!parsed) {
return;
}
return fitImageSize(
parsed,
container.width(),
container.height() - spacing
);
},
/**
* @desc Get transform value based on the imageSize. Used for ZoomFromOrigin option
* @param {jQuery Element}
* @returns {String} Transform CSS string
*/
getTransform(el, container, top, bottom, imageSize) {
if (!imageSize || imageSize.width <= 0 || imageSize.height <= 0) {
return;
}
const origin = $LG(el).find("img").first().get() || el;
if (!origin) {
return;
}
const containerRect = container.get().getBoundingClientRect();
const rect = origin.getBoundingClientRect();
const triggerRect = {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height
};
if (!isUsableOriginRect(triggerRect)) {
return;
}
return getOriginTransform({
triggerRect,
containerRect: {
left: containerRect.left,
top: containerRect.top,
width: containerRect.width,
// Element height, not rect height, includes the mobile
// safari bottom bar handling this always had.
height: container.height()
},
top,
bottom,
imageSize
});
},
getIframeMarkup(iframeWidth, iframeHeight, iframeMaxWidth, iframeMaxHeight, src, iframeTitle) {
const title = iframeTitle ? 'title="' + iframeTitle + '"' : "";
return `<div class="lg-media-cont lg-has-iframe" style="width:${iframeWidth}; max-width:${iframeMaxWidth}; height: ${iframeHeight}; max-height:${iframeMaxHeight}">
<iframe class="lg-object" frameborder="0" ${title} src="${src}" allowfullscreen="true"></iframe>
</div>`;
},
getImgMarkup(index, src, altAttr, srcset, sizes, sources) {
const srcsetAttr = srcset ? `srcset="${srcset}"` : "";
const sizesAttr = sizes ? `sizes="${sizes}"` : "";
const imgMarkup = `<img ${altAttr} ${srcsetAttr} ${sizesAttr} class="lg-object lg-image" data-index="${index}" src="${src}" />`;
let sourceTag = "";
if (sources) {
const sourceObj = typeof sources === "string" ? JSON.parse(sources) : sources;
sourceTag = sourceObj.map((source) => {
let attrs = "";
Object.keys(source).forEach((key) => {
attrs += ` ${key}="${source[key]}"`;
});
return `<source ${attrs}></source>`;
});
}
return `${sourceTag}${imgMarkup}`;
},
// Get src from responsive src
getResponsiveSrc(srcItms) {
const rsWidth = [];
const rsSrc = [];
let src = "";
for (let i = 0; i < srcItms.length; i++) {
const _src = srcItms[i].split(" ");
if (_src[0] === "") {
_src.splice(0, 1);
}
rsSrc.push(_src[0]);
rsWidth.push(_src[1]);
}
const wWidth = window.innerWidth;
for (let j = 0; j < rsWidth.length; j++) {
if (parseInt(rsWidth[j], 10) > wWidth) {
src = rsSrc[j];
break;
}
}
return src;
},
isImageLoaded(img) {
if (!img) return false;
if (!img.complete) {
return false;
}
if (img.naturalWidth === 0) {
return false;
}
return true;
},
getVideoPosterMarkup(_poster, dummyImg, videoContStyle, playVideoString, _isVideo) {
let videoClass = "";
if (_isVideo && _isVideo.youtube) {
videoClass = "lg-has-youtube";
} else if (_isVideo && _isVideo.vimeo) {
videoClass = "lg-has-vimeo";
} else if (_isVideo && _isVideo.wistia) {
videoClass = "lg-has-wistia";
} else {
videoClass = "lg-has-html5";
}
let _dummy = dummyImg;
if (typeof dummyImg !== "string") {
_dummy = dummyImg.outerHTML;
}
return `<div class="lg-video-cont ${videoClass}" style="${videoContStyle}">
<div class="lg-video-play-button">
<svg
viewBox="0 0 20 20"
preserveAspectRatio="xMidYMid"
focusable="false"
aria-labelledby="${playVideoString}"
role="img"
class="lg-video-play-icon"
>
<title>${playVideoString}</title>
<polygon class="lg-video-play-icon-inner" points="1,0 20,10 1,20"></polygon>
</svg>
<svg class="lg-video-play-icon-bg" viewBox="0 0 50 50" focusable="false">
<circle cx="50%" cy="50%" r="20"></circle></svg>
<svg class="lg-video-play-icon-circle" viewBox="0 0 50 50" focusable="false">
<circle cx="50%" cy="50%" r="20"></circle>
</svg>
</div>
${_dummy}
<img class="lg-object lg-video-poster" src="${_poster}" />
</div>`;
},
getFocusableElements(container) {
const elements = container.querySelectorAll(
'a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'
);
const visibleElements = [].filter.call(
elements,
(element) => {
const style = window.getComputedStyle(element);
return style.display !== "none" && style.visibility !== "hidden" && // Hidden or moved into the toolbar's More menu, whatever
// the stylesheet says about their display.
!element.closest("[hidden], [data-lg-overflow]");
}
);
return visibleElements;
},
/**
* @desc Create dynamic elements array from gallery items when dynamic option is false
* It helps to avoid frequent DOM interaction
* and avoid multiple checks for dynamic elments
*
* @returns {Array} dynamicEl
*/
getDynamicOptions(items, extraProps, getCaptionFromTitleOrAlt, exThumbImage) {
const dynamicElements = [];
const availableDynamicOptions = [
...defaultDynamicOptions,
...extraProps
];
[].forEach.call(items, (item) => {
const dynamicEl = {};
for (let i = 0; i < item.attributes.length; i++) {
const attr = item.attributes[i];
if (attr.specified) {
const dynamicAttr = convertToData(attr.name);
let label = "";
if (availableDynamicOptions.indexOf(dynamicAttr) > -1) {
label = dynamicAttr;
}
if (label) {
dynamicEl[label] = attr.value;
}
}
}
if (typeof dynamicEl.sources === "string") {
try {
dynamicEl.sources = JSON.parse(dynamicEl.sources);
} catch (e) {
console.warn(
"lightGallery :- data-sources must be a JSON array of picture source objects. See https://www.lightgalleryjs.com/docs/responsive-loading/"
);
delete dynamicEl.sources;
}
}
const currentItem = $LG(item);
const alt = currentItem.find("img").first().attr("alt");
const title = currentItem.attr("title");
const thumb = exThumbImage ? currentItem.attr(exThumbImage) : currentItem.find("img").first().attr("src");
dynamicEl.thumb = thumb;
if (getCaptionFromTitleOrAlt && !dynamicEl.subHtml) {
dynamicEl.subHtml = title || alt || "";
}
dynamicEl.alt = alt || title || "";
dynamicElements.push(dynamicEl);
});
return dynamicElements;
},
isMobile() {
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
},
/**
* @desc Check the given src is video
* @param {String} src
* @return {Object} video type
* Ex:{ youtube : ["//www.youtube.com/watch?v=c0asJgSyxcY", "c0asJgSyxcY"] }
*
* @todo - this information can be moved to dynamicEl to avoid frequent calls
*/
isVideo(src, isHTML5VIdeo, index) {
if (!src) {
if (isHTML5VIdeo) {
return {
html5: true
};
} else {
console.error(
"lightGallery :- data-src is not provided on slide item " + (index + 1) + ". Please make sure the selector property is properly configured. More info - https://www.lightgalleryjs.com/demos/html-markup/"
);
return;
}
}
return getVideoInfo(src, false);
}
};
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
let lgId = 0;
class LightGallery {
constructor(element, options) {
this.lgOpened = false;
this.index = 0;
this.plugins = [];
this.lGalleryOn = false;
this.lgBusy = false;
this.defaultIcons = __spreadValues({}, coreDefaultIcons);
this.swipeSamples = [];
this.currentItemsInDom = [];
this.prevScrollTop = 0;
this.bodyPaddingRight = 0;
this.isDummyImageRemoved = false;
this.dragOrSwipeEnabled = false;
this.mediaContainerPosition = {
top: 0,
bottom: 0
};
if (!element) {
return this;
}
lgId++;
this.lgId = lgId;
this.el = element;
this.LGel = $LG(element);
this.generateSettings(options);
this.buildModules();
if (this.settings.dynamic && this.settings.dynamicEl !== void 0 && !Array.isArray(this.settings.dynamicEl)) {
throw "When using dynamic mode, you must also define dynamicEl as an Array.";
}
this.galleryItems = this.getItems();
this.normalizeSettings();
this.init();
this.validateLicense();
return this;
}
generateSettings(options) {
var _a;
this.settings = __spreadProps(__spreadValues(__spreadValues({}, lightGalleryCoreSettings), options), {
// Strings merge per-key (headless resolveSettings parity), // a partial override keeps every other default.
strings: __spreadValues(__spreadValues({}, lightGalleryCoreSettings.strings), (_a = options == null ? void 0 : options.strings) != null ? _a : {})
});
if (this.settings.isMobile && typeof this.settings.isMobile === "function" ? this.settings.isMobile() : utils.isMobile()) {
const mobileSettings = __spreadValues({}, this.settings.mobileSettings);
this.settings = __spreadValues(__spreadValues({}, this.settings), mobileSettings);
}
if (typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
this.settings = __spreadProps(__spreadValues({}, this.settings), {
speed: 0,
backdropDuration: 0,
startAnimationDuration: 0,
zoomFromOrigin: false,
slideEndAnimation: false
});
}
}
normalizeSettings() {
if (this.settings.direction === "auto") {
this.settings.direction = typeof window !== "undefined" && typeof window.getComputedStyle === "function" && window.getComputedStyle(this.el).direction === "rtl" ? "rtl" : "ltr";
}
if (this.settings.slideEndAnimation) {
this.settings.hideControlOnEnd = false;
}
if (!this.settings.closable) {
this.settings.swipeToClose = false;
}
this.zoomFromOrigin = this.settings.zoomFromOrigin;
if (this.settings.container) {
const { container } = this.settings;
if (typeof container === "function") {
this.settings.container = container();
} else if (typeof container === "string") {
const el = document.querySelector(container);
this.settings.container = el != null ? el : document.body;
}
} else {
this.settings.container = document.body;
}
this.settings.preload = Math.min(
this.settings.preload,
this.galleryItems.length
);
}
init() {
this.addSlideVideoInfo(this.galleryItems);
this.buildStructure();
this.LGel.trigger(lGEvents.init, {
instance: this
});
if (this.settings.keyPress) {
this.keyPress();
}
setTimeout(() => {
this.enableDrag();
this.enableSwipe();
this.triggerPosterClick();
}, 50);
this.arrow();
if (this.settings.mousewheel) {
this.mousewheel();
}
if (!this.settings.dynamic) {
this.openGalleryOnItemClick();
}
}
openGalleryOnItemClick() {
for (let index = 0; index < this.items.length; index++) {
const element = this.items[index];
const $element = $LG(element);
const uuid = lgQuery.generateUUID();
$element.attr("data-lg-id", uuid).on(`click.lgcustom-item-${uuid}`, (e) => {
e.preventDefault();
const currentItemIndex = this.settings.index || index;
this.openGallery(currentItemIndex, element);
});
}
}
/**
* Module constructor
* Modules are build incrementally.
* Gallery should be opened only once all the modules are initialized.
* use moduleBuildTimeout to make sure this
*/
buildModules() {
this.settings.plugins.forEach((plugin) => {
this.plugins.push(new plugin(this, $LG));
});
}
validateLicense() {
const notice = takeLicenseNotice(this.settings.licenseKey);
if (notice) console[notice.level](notice.message);
}
getSlideItem(index) {
return $LG(this.getSlideItemId(index));
}
getSlideItemId(index) {
return `#lg-item-${this.lgId}-${index}`;
}
getIdName(id) {
return `${id}-${this.lgId}`;
}
getElementById(id) {
return $LG(`#${this.getIdName(id)}`);
}
manageSingleSlideClassName() {
if (this.galleryItems.length < 2) {
this.outer.addClass("lg-single-item");
} else {
this.outer.removeClass("lg-single-item");
}
}
buildStructure() {
const container = this.$container && this.$container.get();
if (container) {
return;
}
let controls = "";
let subHtmlCont = "";
if (this.settings.controls) {
controls = `<button type="button" id="${this.getIdName(
"lg-prev"
)}" aria-label="${this.settings.strings["previousSlide"]}" class="lg-prev lg-icon"> ${this.settings.prevHtml} </button>
<button type="button" id="${this.getIdName(
"lg-next"
)}" aria-label="${this.settings.strings["nextSlide"]}" class="lg-next lg-icon"> ${this.settings.nextHtml} </button>`;
}
if (this.settings.appendSubHtmlTo !== ".lg-item") {
subHtmlCont = this.settings.ariaAnnouncements ? '<div class="lg-sub-html"></div>' : '<div class="lg-sub-html" role="status" aria-live="polite"></div>';
}
let addClasses = "";
if (this.settings.allowMediaOverlap) {
addClasses += "lg-media-overlap ";
}
const ariaLabelledby = this.settings.ariaLabelledby ? 'aria-labelledby="' + this.settings.ariaLabelledby + '"' : `aria-label="${this.settings.strings["galleryLabel"]}"`;
const ariaDescribedby = this.settings.ariaDescribedby ? 'aria-describedby="' + this.settings.ariaDescribedby + '"' : "";
const containerClassName = `lg-container ${this.settings.addClass} ${document.body !== this.settings.container ? "lg-inline" : ""}`;
const closeIcon = this.settings.closable && this.settings.showCloseIcon ? `<button type="button" aria-label="${this.settings.strings["closeGallery"]}" id="${this.getIdName(
"lg-close"
)}" class="lg-close lg-icon"></button>` : "";
const maximizeIcon = this.settings.showMaximizeIcon ? `<button type="button" aria-label="${this.settings.strings["toggleMaximize"]}" id="${this.getIdName(
"lg-maximize"
)}" class="lg-maximize lg-icon"></button>` : "";
const template = `
<div class="${containerClassName}" id="${this.getIdName(
"lg-container"
)}" tabindex="-1" aria-modal="true" ${ariaLabelledby} ${ariaDescribedby} role="dialog" dir="${this.getDirection()}"
>
<div id="${this.getIdName(
"lg-backdrop"
)}" class="lg-backdrop"></div>
${this.settings.ariaAnnouncements ? `<div id="${this.getIdName(
"lg-announcer"
)}" class="lg-announcer" role="status" aria-live="polite"></div>` : ""}
<div id="${this.getIdName(
"lg-outer"
)}" class="lg-outer lg-use-css3 lg-css3 lg-hide-items ${addClasses} ">
<div id="${this.getIdName("lg-content")}" class="lg-content">
<div id="${this.getIdName("lg-inner")}" class="lg-inner">
</div>
${controls}
</div>
<div id="${this.getIdName(
"lg-toolbar"
)}" class="lg-toolbar lg-group">
${maximizeIcon}
${closeIcon}
</div>
${this.settings.appendSubHtmlTo === ".lg-outer" ? subHtmlCont : ""}
<div id="${this.getIdName(
"lg-components"
)}" class="lg-components">
${this.settings.appendSubHtmlTo === ".lg-sub-html" ? subHtmlCont : ""}
</div>
</div>
</div>
`;
$LG(this.settings.container).append(template);
if (document.body !== this.settings.container) {
$LG(this.settings.container).css("position", "relative");
}
this.outer = this.getElementById("lg-outer");
this.$lgComponents = this.getElementById("lg-components");
this.$backdrop = this.getElementById("lg-backdrop");
this.$container = this.getElementById("lg-container");
this.$inner = this.getElementById("lg-inner");
this.$content = this.getElementById("lg-content");
this.$toolbar = this.getElementById("lg-toolbar");
this.$backdrop.css(
"transition-duration",
this.settings.backdropDuration + "ms"
);
let outerClassNames = `${this.settings.mode} `;
this.manageSingleSlideClassName();
if (this.settings.enableDrag) {
outerClassNames += "lg-grab ";
}
this.outer.addClass(outerClassNames);
this.$inner.css("transition-timing-function", this.settings.easing);
this.$inner.css("transition-duration", this.settings.speed + "ms");
this.$inner.css("--lg-speed", this.settings.speed + "ms");
if (this.settings.download) {
this.$toolbar.append(
`<a id="${this.getIdName(
"lg-download"
)}" target="_blank" rel="noopener" aria-label="${this.settings.strings["download"]}" download class="lg-download lg-icon"></a>`
);
}
this.counter();
$LG(window).on(
`resize.lg.global${this.lgId} orientationchange.lg.global${this.lgId}`,
() => {
this.refreshOnResize();
}
);
this.hideBars();
this.manageCloseGallery();
this.toggleMaximize();
this.initModules();
if (this.settings.toolbarOverflow) {
this.toolbarOverflow = new ToolbarOverflow(this.$toolbar.get(), {
id: this.getIdName("lg-more"),
label: this.settings.strings.moreOptions
});
const update = () => {
var _a;
return (_a = this.toolbarOverflow) == null ? void 0 : _a.update();
};
this.LGel.on(`${lGEvents.afterOpen}.lg`, update);
this.LGel.on(`${lGEvents.afterSlide}.lg`, update);
this.LGel.on(`${lGEvents.containerResize}.lg`, update);
this.LGel.on(
`${lGEvents.beforeClose}.lg`,
() => {
var _a;
return (_a = this.toolbarOverflow) == null ? void 0 : _a.close(false);
}
);
}
applyCustomIcons(
this.$container.get(),
this.defaultIcons,
this.settings.icons
);
}
/**
* Merge a plugin's built-in icon SVGs into the default set (called
* from plugin `init()`, before the icon pass runs). Consumer
* `settings.icons` still win over anything registered here.
*/
registerDefaultIcons(icons) {
this.defaultIcons = __spreadValues(__spreadValues({}, this.defaultIcons), icons);
}
/**
* Cache the fitted (contain) size of a slide's media. Every consumer
* of `currentImageSize` measures against the CURRENT slide: the
* actual-size zoom divides the natural width by it, and the
* zoom-from-origin close flight positions against it. Recomputed on
* every slide change, not just at open — one cached landscape size
* makes a portrait slide zoom to the wrong scale and then snap to
* its real size when the natural-px swap lands.
*/
updateCurrentImageSize(index) {
const { __slideVideoInfo, lgSize } = this.galleryItems[index];
const { top, bottom } = this.mediaContainerPosition;
this.currentImageSize = utils.getSize(
this.items[index],
this.outer,
top + bottom,
__slideVideoInfo && this.settings.videoMaxSize,
lgSize
);
}
refreshOnResize() {
if (this.lgOpened) {
const currentGalleryItem = this.galleryItems[this.index];
const { __slideVideoInfo } = currentGalleryItem;
this.mediaContainerPosition = this.getMediaContainerPosition();
this.updateCurrentImageSize(this.index);
if (__slideVideoInfo) {
this.resizeVideoSlide(this.index, this.currentImageSize);
}
if (this.zoomFromOrigin && !this.isDummyImageRemoved) {
const imgStyle = this.getDummyImgStyles(this.currentImageSize);
this.outer.find(".lg-current .lg-dummy-img").first().attr("style", imgStyle);
}
this.LGel.trigger(lGEvents.containerResize);
}
}
resizeVideoSlide(index, imageSize) {
const lgVideoStyle = this.getVideoContStyle(imageSize);
const currentSlide = this.getSlideItem(index);
currentSlide.find(".lg-video-cont").attr("style", lgVideoStyle);
}
/**
* Update slides dynamically.
* Add, edit or delete slides dynamically when lightGallery is opened.
* Modify the current gallery items and pass it via updateSlides method
* @note
* - Do not mutate existing lightGallery items directly.
* - Always pass a new list of gallery items
* - You need to take care of thumbnails outside the gallery if any
* - use this method only if you want to update slides when the gallery is opened. Otherwise, use `refresh()` method.
* @param items Gallery items
* @param index After the update operation, which slide gallery should navigate to
* @category lGPublicMethods
* @example
* const plugin = lightGallery();
*
* // Adding slides dynamically
* let galleryItems = [
* // Access existing lightGallery items
* // galleryItems are automatically generated internally from the gallery HTML markup
* // or directly from galleryItems when dynamic gallery is used
* ...plugin.galleryItems,
* ...[
* {
* src: 'img/img-1.png',
* thumb: 'img/thumb1.png',
* },
* ],
* ];
* plugin.updateSlides(
* galleryItems,
* plugin.index,
* );
*
*
* // Remove slides dynamically
* galleryItems = JSON.parse(
* JSON.stringify(plugin.galleryItems),
* );
* galleryItems.shift();
* plugin.updateSlides(galleryItems, 1);
* @see <a href="/demos/update-slides/">Demo</a>
*/
updateSlides(items, index) {
if (this.index > items.length - 1) {
this.index = items.length - 1;
}
if (items.length === 1) {
this.index = 0;
}
if (!items.length) {
this.closeGallery();
return;
}
const currentSrc = this.galleryItems[index].src;
this.galleryItems = items;
this.updateControls();
this.$inner.empty();
this.currentItemsInDom = [];
let _index = 0;
this.galleryItems.some((galleryItem, itemIndex) => {
if (galleryItem.src === currentSrc) {
_index = itemIndex;
return true;
}
return false;
});
this.currentItemsInDom = this.organizeSlideItems(_index, -1);
this.loadContent(_index, true);
this.getSlideItem(_index).addClass("lg-current");
this.index = _index;
this.updateCurrentCounter(_index);
this.LGel.trigger(lGEvents.updateSlides);
}
// Get gallery items based on multiple conditions
getItems() {
this.items = [];
if (!this.settings.dynamic) {
if (this.settings.selector === "this") {
this.items.push(this.el);
} else if (this.settings.selector) {
if (typeof this.settings.selector === "string") {
if (this.settings.selectWithin) {
const selectWithin = $LG(this.settings.selectWithin);
this.items = selectWithin.find(this.settings.selector).get();
} else {
this.items = this.el.querySelectorAll(
this.settings.selector
);
}
} else {
this.items = this.settings.selector;
}
} else {
this.items = this.el.children;
}
return utils.getDynamicOptions(
this.items,
this.settings.extraProps,
this.settings.getCaptionFromTitleOrAlt,
this.settings.exThumbImage
);
} else {
return this.settings.dynamicEl || [];
}
}
shouldHideScrollbar() {
return this.settings.hideScrollbar && document.body === this.settings.container;
}
hideScrollbar() {
if (!this.shouldHideScrollbar()) {
return;
}
this.bodyPaddingRight = parseFloat($LG("body").style().paddingRight);
const bodyRect = document.documentElement.getBoundingClientRect();
const scrollbarWidth = window.innerWidth - bodyRect.width;
$LG(document.body).css(
"padding-right",
scrollbarWidth + this.bodyPaddingRight + "px"
);
$LG(document.body).addClass("lg-overlay-open");
}
resetScrollBar() {
if (!this.shouldHideScrollbar()) {
return;
}
$LG(document.body).css("padding-right", this.bodyPaddingRight + "px");
$LG(document.body).removeClass("lg-overlay-open");
}
/**
* Open lightGallery.
* Open gallery with specific slide by passing index of the slide as parameter.
* @category lGPublicMethods
* @param {Number} index - index of the slide
* @param {HTMLElement} element - Which image lightGallery should zoom from
*
* @example
* const $dynamicGallery = document.getElementById('dynamic-gallery-demo');
* const dynamicGallery = lightGallery($dynamicGallery, {
* dynamic: true,
* dynamicEl: [
* {
* src: 'img/1.jpg',
* thumb: 'img/thumb-1.jpg',
* subHtml: '<h4>Image 1 title</h4><p>Image 1 descriptions.</p>',
* },
* ...
* ],
* });
* $dynamicGallery.addEventListener('click', function () {
* // Starts with third item.(Optional).
* // This is useful if you want use dynamic mode with
* // custom thumbnails (thumbnails outside gallery),
* dynamicGallery.openGallery(2);
* });
*
*/
openGallery(index = this.settings.index, element) {
if (this.lgOpened) return;
this.lgOpened = true;
this.outer.removeClass("lg-hide-items");
this.hideScrollbar();
this.$container.addClass("lg-show");
const itemsToBeInsertedToDom = this.getItemsToBeInsertedToDom(
index,
index
);
this.currentItemsInDom = itemsToBeInsertedToDom;
let items = "";
itemsToBeInsertedToDom.forEach((item) => {
items = items + `<div id="${item}" class="lg-item"></div>`;
});
this.$inner.append(items);
this.addHtml(index);
let transform = "";
this.mediaContainerPosition = this.getMediaContainerPosition();
const { top, bottom } = this.mediaContainerPosition;
if (!this.settings.allowMediaOverlap) {
this.setMediaContainerPosition(top, bottom);
}
const { __slideVideoInfo, lgSize } = this.galleryItems[index];
if (this.zoomFromOrigin && element) {
this.currentImageSize = utils.getSize(
element,
this.outer,
top + bottom,
__slideVideoInfo && this.settings.videoMaxSize,
lgSize
);
transform = this.getOriginTransform(element, this.currentImageSize);
}
if (!this.zoomFromOrigin || !transform) {
this.outer.addClass(this.settings.startClass);
this.getSlideItem(index).removeClass("lg-complete");
}
const timeout = this.settings.zoomFromOrigin ? 100 : this.settings.backdropDuration;
setTimeout(() => {
this.outer.addClass("lg-components-open");
}, timeout);
this.index = index;
this.LGel.trigger(lGEvents.beforeOpen);
this.getSlideItem(index).addClass("lg-current");
this.lGalleryOn = false;
this.prevScrollTop = $LG(window).scrollTop();
if (this.settings.trapFocus && document.body === this.settings.container) {
this.prevActiveElement = document.activeElement instanceof HTMLElement ? document.activeElement : void 0;
}
setTimeout(() => {
if (this.zoomFromOrigin && transform) {
const currentSlide = this.getSlideItem(index);
currentSlide.css("transform", transform);
setTimeout(() => {
currentSlide.addClass("lg-start-progress lg-start-end-progress").css(
"transition-duration",
this.settings.startAnimationDuration + "ms"
);
this.outer.addClass("lg-zoom-from-image");
});
setTimeout(() => {
currentSlide.css("transform", "translate3d(0, 0, 0)");
}, 100);
}
setTimeout(() => {
this.$backdrop.addClass("in");
this.$container.addClass("lg-show-in");
}, 10);
setTimeout(() => {
if (this.settings.trapFocus && document.body === this.settings.container) {
this.trapFocus();
}
}, this.settings.backdropDuration + 50);
if (!this.zoomFromOrigin || !transform) {
setTimeout(() => {
this.outer.addClass("lg-visible");
}, this.settings.backdropDuration);
}
this.slide(index, false, false, false);
this.LGel.trigger(lGEvents.afterOpen);
});
if (document.body === this.settings.container) {
$LG("html").addClass("lg-on");
}
}
/**
* The zoom-from-origin transform for a trigger: it lands the slide on
* the trigger's thumbnail, the start of the opening flight and the end
* of the closing one. Undefined when the trigger cannot anchor a flight,
* and the gallery opens or closes about the stage centre instead.
* Plugins may replace it on the instance to fly differently.
*/
getOriginTransform(element, imageSize) {
const { top, bottom } = this.mediaContainerPosition;
return utils.getTransform(element, this.outer, top, bottom, imageSize);
}
/**
* Note - Changing the position of the media on every slide transition creates a flickering effect.
* Therefore, the height of the caption is calculated dynamically, only once based on the first slide caption.
* if you have dynamic captions for each media,
* you can provide an appropriate height for the captions via defaultCaptionHeight option
*/
getMediaContainerPosition() {
if (this.settings.allowMediaOverlap) {
return {
top: 0,
bottom: 0
};
}
const top = this.$toolbar.get().clientHeight || 0;
const subHtml = this.outer.find(".lg-components .lg-sub-html").get();
const captionHeight = this.settings.defaultCaptionHeight || subHtml && subHtml.clientHeight || 0;
const thumbContainer = this.outer.find(".lg-thumb-outer").get();
const thumbHeight = thumbContainer ? thumbContainer.clientHeight : 0;
const bottom = thumbHeight + captionHeight;
return {
top,
bottom
};
}
setMediaContainerPosition(top = 0, bottom = 0) {
this.$content.css("top", top + "px").css("bottom", bottom + "px");
}
hideBars() {
setTimeout(() => {
this.outer.removeClass("lg-hide-items");
if (this.settings.hideBarsDelay > 0) {
this.outer.on("mousemove.lg click.lg touchstart.lg", () => {
this.outer.removeClass("lg-hide-items");
clearTimeout(this.hideBarTimeout);
this.hideBarTimeout = setTimeout(() => {
this.outer.addClass("lg-hide-items");
}, this.settings.hideBarsDelay);
});
this.outer.trigger("mousemove.lg");
}
}, this.settings.showBarsAfter);
}
/**
* @desc Create image counter
* Ex: 1/10
*/
counter() {
if (this.settings.counter) {
const counterA11yAttrs = this.settings.ariaAnnouncements ? 'aria-hidden="true"' : 'role="status" aria-live="polite"';
const counterHtml = `<div class="lg-counter" ${counterA11yAttrs}>
<span id="${this.getIdName(
"lg-counter-current"
)}" class="lg-counter-current">${this.index + 1} </span> /
<span id="${this.getIdName(
"lg-counter-all"
)}" class="lg-counter-all">${this.galleryItems.length} </span></div>`;
this.outer.find(this.settings.appendCounterTo).append(counterHtml);
}
}
/**
* @desc add sub-html into the slide
* @param {Number} index - index of the slide
*/
addHtml(index) {
let subHtml;
let subHtmlUrl;
if (this.galleryItems[index].subHtmlUrl) {
subHtmlUrl = this.galleryItems[index].subHtmlUrl;
} else {
subHtml = this.galleryItems[index].subHtml;
}
if (!subHtmlUrl) {
if (subHtml) {
const fL = subHtml.substring(0, 1);
if (fL === "." || fL === "#") {
try {
if (this.settings.subHtmlSelectorRelative && !this.settings.dynamic) {
subHtml = $LG(this.items).eq(index).find(subHtml).first().html();
} else {
subHtml = $LG(subHtml).first().html();
}
} catch (error) {
console.warn(
`lightGallery: error processing subHtml selector "${subHtml}". See https://www.lightgalleryjs.com/demos/captions/`
);
subHtml = "";
}
}
} else {
subHtml = "";
}
}
if (this.settings.appendSubHtmlTo !== ".lg-item") {
if (subHtmlUrl) {
utils.fetchCaptionFromUrl(
subHtmlUrl,
this.outer.find(".lg-sub-html"),
"replace"
);
} else {
this.outer.find(".lg-sub-html").html(subHtml);
}
} else {
const currentSlide = $LG(this.getSlideItemId(index));
if (subHtmlUrl) {
utils.fetchCaptionFromUrl(subHtmlUrl, currentSlide, "append");
} else {
currentSlide.append(
`<div class="lg-sub-html">${subHtml}</div>`
);
}
}
if (typeof subHtml !== "undefined" && subHtml !== null) {
if (subHtml === "") {
this.outer.find(this.settings.appendSubHtmlTo).addClass("lg-empty-html");
} else {
this.outer.find(this.settings.appendSubHtmlTo).removeClass("lg-empty-html");
}
}
this.LGel.trigger(
lGEvents.afterAppendSubHtml,
{
index
}
);
}
/**
* @desc Preload slides
* @param {Number} index - index of the slide
* @todo preload not working for the first slide, Also, should work for the first and last slide as well
*/
preload(index) {
for (let i = 1; i <= this.settings.preload; i++) {
if (i >= this.galleryItems.length - index) {
break;
}
this.loadContent(index + i, false);
}
for (let j = 1; j <= this.settings.preload; j++) {
if (index - j < 0) {
break;
}
this.loadContent(index - j, false);
}
}
getDummyImgStyles(imageSize) {
if (!imageSize) return "";
return `width:${imageSize.width}px;
margin-left: -${imageSize.width / 2}px;
margin-top: -${imageSize.height / 2}px;
height:${imageSize.height}px`;
}
getVideoContStyle(imageSize) {
if (!imageSize) return "";
return `width:${imageSize.width}px;
height:${imageSize.height}px`;
}
getDummyImageContent($currentSlide, index, alt) {
let $currentItem;
if (!this.settings.dynamic) {
$currentItem = $LG(this.items).eq(index);
}
if ($currentItem) {
let _dummyImgSrc;
if (!this.settings.exThumbImage) {
_dummyImgSrc = $currentItem.find("img").first().attr("src");
} else {
_dummyImgSrc = $currentItem.attr(this.settings.exThumbImage);
}
if (!_dummyImgSrc) return "";
const imgStyle = this.getDummyImgStyles(this.currentImageSize);
const dummyImgContentImg = document.createElement("img");
dummyImgContentImg.alt = alt || "";
dummyImgContentImg.src = _dummyImgSrc;
dummyImgContentImg.className = `lg-dummy-img`;
dummyImgContentImg.style.cssText = imgStyle;
$currentSlide.addClass("lg-first-slide");
this.outer.addClass("lg-first-slide-loading");
return dummyImgContentImg;
}
return "";
}
setImgMarkup(src, $currentSlide, index) {
const currentGalleryItem = this.galleryItems[index];
const { alt, srcset, sizes, sources } = currentGalleryItem;
let imgContent = "";
const altAttr = alt ? 'alt="' + alt + '"' : "";
if (this.isFirstSlideWithZoomAnimation()) {
imgContent = this.getDummyImageContent(
$currentSlide,
index,
altAttr
);
} else {
imgContent = utils.getImgMarkup(
index,
src,
altAttr,
srcset,
sizes,
sources
);
}
const picture = document.createElement("picture");
picture.className = "lg-img-wrap";
$LG(picture).append(imgContent);
$currentSlide.prepend(picture);
}
onSlideObjectLoad($slide, isHTML5VideoWithoutPoster, onLoad, onError) {
const mediaObject = $slide.find(".lg-object").first();
const media = mediaObject.get();
const gated = (handler) => {
if (media instanceof HTMLImageElement && typeof media.decode === "function") {
void awaitDecode(media).then(handler);
} else {
handler();
}
};
if (utils.isImageLoaded(media) || isHTML5VideoWithoutPoster) {
gated(onLoad);
} else {
mediaObject.on("load.lg error.lg", () => {
gated(() => onLoad && onLoad());
});
mediaObject.on("error.lg", () => {
onError && onError();
});
}
}
/**
*
* @param $el Current slide item
* @param index
* @param delay Delay is 0 except first time
* @param speed Speed is same as delay, except it is 0 if gallery is opened via hash plugin
* @param isFirstSlide
*/
onLgObjectLoad(currentSlide, index, delay, speed, isFirstSlide, isHTML5VideoWithoutPoster) {
this.onSlideObjectLoad(
currentSlide,
isHTML5VideoWithoutPoster,
() => {
this.triggerSlideItemLoad(
currentSlide,
index,
delay,
speed,
isFirstSlide
);
},
() => {
currentSlide.addClass("lg-complete lg-complete_");
currentSlide.html(
'<span class="lg-error-msg">' + this.settings.strings["mediaLoadingFailed"] + "</span>"
);
}
);
}
triggerSlideItemLoad($currentSlide, index, delay, speed, isFirstSlide) {
const currentGalleryItem = this.galleryItems[index];
const _speed = isFirstSlide && this.getSlideType(currentGalleryItem) === "video" && !currentGalleryItem.poster ? speed : 0;
setTimeout(() => {
$currentSlide.addClass("lg-complete lg-complete_");
this.LGel.trigger(lGEvents.slideItemLoad, {
index,
delay: delay || 0,
isFirstSlide
});
}, _speed);
}
isFirstSlideWithZoomAnimation() {
return !!(!this.lGalleryOn && this.zoomFromOrigin && this.currentImageSize);
}
// Add video slideInfo
addSlideVideoInfo(items) {
items.forEach((element, index) => {
element.__slideVideoInfo = utils.isVideo(
element.src,
!!element.video,
index
);
const videoInfo = element.__slideVideoInfo;
if (!videoInfo) {
return;
}
if (this.settings.videoFacade !== false) {
element.poster = getFacadePoster(
element,
videoInfo,
this.settings.loadYouTubePoster
);
} else if (this.settings.loadYouTubePoster && !element.poster && videoInfo.youtube) {
element.poster = getYouTubePosterUrl(videoInfo);
}
});
}
/**
* Load slide content into slide.
* This is used to load content into slides that is not visible too
* @param {Number} index - index of the slide.
* @param {Boolean} rec - if true call loadcontent() function again.
*/
loadContent(index, rec) {
const currentGalleryItem = this.galleryItems[index];
const $currentSlide = $LG(this.getSlideItemId(index));
const { poster, srcset, sizes, sources } = currentGalleryItem;
let { src } = currentGalleryItem;
const video = currentGalleryItem.video;
const _html5Video = video && typeof video === "string" ? JSON.parse(video) : video;
if (currentGalleryItem.responsive) {
const srcDyItms = currentGalleryItem.responsive.split(",");
src = utils.getResponsiveSrc(srcDyItms) || src;
}
const videoInfo = currentGalleryItem.__slideVideoInfo;
let lgVideoStyle = "";
const iframe = !!currentGalleryItem.iframe;
const isFirstSlide = !this.lGalleryOn;
let delay = 0;
if (isFirstSlide) {
if (this.zoomFromOrigin && this.currentImageSize) {
delay = this.settings.startAnimationDuration + 10;
} else {
delay = this.settings.backdropDuration + 10;
}
}
if (!$currentSlide.hasClass("lg-loaded")) {
if (videoInfo) {
const { top, bottom } = this.mediaContainerPosition;
const videoSize = utils.getSize(
this.items[index],
this.outer,
top + bottom,
videoInfo && this.settings.videoMaxSize,
this.galleryItems[index].lgSize
);
lgVideoStyle = this.getVideoContStyle(videoSize);
}
if (iframe) {
const markup = utils.getIframeMarkup(
this.settings.iframeWidth,
this.settings.iframeHeight,
this.settings.iframeMaxWidth,
this.settings.iframeMaxHeight,
src,
currentGalleryItem.iframeTitle
);
$currentSlide.prepend(markup);
} else if (poster) {
let dummyImg = "";
const hasStartAnimation = isFirstSlide && this.zoomFromOrigin && this.currentImageSize;
if (hasStartAnimation) {
dummyImg = this.getDummyImageContent(
$currentSlide,
index,
""
);
}
const markup = utils.getVideoPosterMarkup(
poster,
dummyImg || "",
lgVideoStyle,
this.settings.strings["playVideo"],
videoInfo
);
$currentSlide.prepend(markup);
} else if (videoInfo) {
const markup = `<div class="lg-video-cont " style="${lgVideoStyle}"></div>`;
$currentSlide.prepend(markup);
} else {
this.setImgMarkup(src, $currentSlide, index);
}
if (poster || videoInfo) {
this.LGel.trigger(lGEvents.hasVideo, {
index,
src,
html5Video: _html5Video,
hasPoster: !!poster
});
}
this.LGel.trigger(
lGEvents.afterAppendSlide,
{ index }
);
if (this.lGalleryOn && this.settings.appendSubHtmlTo === ".lg-item") {
this.addHtml(index);
}
}
let _speed = 0;
if (delay && !$LG(document.body).hasClass("lg-from-hash")) {
_speed = delay;
}
if (this.isFirstSlideWithZoomAnimation()) {
const settleFlight = (onLanded) => onTransitionSettle(
$currentSlide.get(),
"transform",
this.settings.startAnimationDuration + 100,
onLanded
);
settleFlight(() => {
$currentSlide.removeClass("lg-start-end-progress lg-start-progress").removeAttr("style");
});
if (!$currentSlide.hasClass("lg-loaded")) {
settleFlight(() => {
if (this.getSlideType(currentGalleryItem) === "image") {
const { alt } = currentGalleryItem;
const altAttr = alt ? 'alt="' + alt + '"' : "";
$currentSlide.find(".lg-img-wrap").append(
utils.getImgMarkup(
index,
src,
altAttr,
srcset,
sizes,
currentGalleryItem.sources
)
);
}
if (this.getSlideType(currentGalleryItem) === "image" || this.getSlideType(currentGalleryItem) === "video" && poster) {
this.onLgObjectLoad(
$currentSlide,
index,
delay,
_speed,
true,
false
);
this.onSlideObjectLoad(
$currentSlide,
!!(videoInfo && videoInfo.html5 && !poster),
() => {
this.loadContentOnFirstSlideLoad(
index,
$currentSlide,
_speed
);
},
() => {
this.loadContentOnFirstSlideLoad(
index,
$currentSlide,
_speed
);
}
);
}
});
}
}
$currentSlide.addClass("lg-loaded");
if (!this.isFirstSlideWithZoomAnimation() || this.getSlideType(currentGalleryItem) === "video" && !poster) {
this.onLgObjectLoad(
$currentSlide,
index,
delay,
_speed,
isFirstSlide,
!!(videoInfo && videoInfo.html5 && !poster)
);
}
if ((!this.zoomFromOrigin || !this.currentImageSize) && $currentSlide.hasClass("lg-complete_") && !this.lGalleryOn) {
setTimeout(() => {
$currentSlide.addClass("lg-complete");
}, this.settings.backdropDuration);
}
this.lGalleryOn = true;
if (rec === true) {
if (!$currentSlide.hasClass("lg-complete_")) {
$currentSlide.find(".lg-object").first().on("load.lg error.lg", () => {
this.preload(index);
});
} else {
this.preload(index);
}
}
}
/**
* @desc Remove dummy image content and load next slides
* Called only for the first time if zoomFromOrigin animation is enabled
* @param index
* @param $currentSlide
* @param speed
*/
loadContentOnFirstSlideLoad(index, $currentSlide, speed) {
setTimeout(() => {
$currentSlide.find(".lg-dummy-img").remove();
$currentSlide.removeClass("lg-first-slide");
this.outer.removeClass("lg-first-slide-loading");
this.isDummyImageRemoved = true;
this.preload(index);
}, speed + 300);
}
getItemsToBeInsertedToDom(index, prevIndex, numberOfItems = 0) {
const itemsToBeInsertedToDom = [];
let possibleNumberOfItems = Math.max(numberOfItems, 3);
possibleNumberOfItems = Math.min(
possibleNumberOfItems,
this.galleryItems.length
);
const prevIndexItem = `lg-item-${this.lgId}-${prevIndex}`;
if (this.galleryItems.length <= 3) {
this.galleryItems.forEach((_element, index2) => {
itemsToBeInsertedToDom.push(`lg-item-${this.lgId}-${index2}`);
});
return itemsToBeInsertedToDom;
}
if (index < (this.galleryItems.length - 1) / 2) {
for (let idx = index; idx > index - possibleNumberOfItems / 2 && idx >= 0; idx--) {
itemsToBeInsertedToDom.push(`lg-item-${this.lgId}-${idx}`);
}
const numberOfExistingItems = itemsToBeInsertedToDom.length;
for (let idx = 0; idx < possibleNumberOfItems - numberOfExistingItems; idx++) {
itemsToBeInsertedToDom.push(
`lg-item-${this.lgId}-${index + idx + 1}`
);
}
} else {
for (let idx = index; idx <= this.galleryItems.length - 1 && idx < index + possibleNumberOfItems / 2; idx++) {
itemsToBeInsertedToDom.push(`lg-item-${this.lgId}-${idx}`);
}
const numberOfExistingItems = itemsToBeInsertedToDom.length;
for (let idx = 0; idx < possibleNumberOfItems - numberOfExistingItems; idx++) {
itemsToBeInsertedToDom.push(
`lg-item-${this.lgId}-${index - idx - 1}`
);
}
}
if (this.settings.loop) {
if (index === this.galleryItems.length - 1) {
itemsToBeInsertedToDom.push(`lg-item-${this.lgId}-${0}`);
} else if (index === 0) {
itemsToBeInsertedToDom.push(
`lg-item-${this.lgId}-${this.galleryItems.length - 1}`
);
}
}
if (itemsToBeInsertedToDom.indexOf(prevIndexItem) === -1) {
itemsToBeInsertedToDom.push(`lg-item-${this.lgId}-${prevIndex}`);
}
return itemsToBeInsertedToDom;
}
organizeSlideItems(index, prevIndex) {
var _a, _b;
const itemsToBeInsertedToDom = this.getItemsToBeInsertedToDom(
index,
prevIndex,
(_b = (_a = this.settings.virtualization) == null ? void 0 : _a.slides) != null ? _b : this.settings.numberOfSlideItemsInDom
);
itemsToBeInsertedToDom.forEach((item) => {
if (this.currentItemsInDom.indexOf(item) === -1) {
this.$inner.append(`<div id="${item}" class="lg-item"></div>`);
}
});
this.currentItemsInDom.forEach((item) => {
if (itemsToBeInsertedToDom.indexOf(item) === -1) {
$LG(`#${item}`).remove();
}
});
return itemsToBeInsertedToDom;
}
/**
* Get previous index of the slide
*/
getPreviousSlideIndex() {
let prevIndex = 0;
try {
const currentItemId = this.outer.find(".lg-current").first().attr("id");
prevIndex = parseInt(currentItemId.split("-")[3]) || 0;
} catch (error) {
prevIndex = 0;
}
return prevIndex;
}
setDownloadValue(index) {
if (this.settings.download) {
const currentGalleryItem = this.galleryItems[index];
const hideDownloadBtn = currentGalleryItem.downloadUrl === false || currentGalleryItem.downloadUrl === "false";
if (hideDownloadBtn) {
this.outer.addClass("lg-hide-download");
} else {
const $download = this.getElementById("lg-download");
this.outer.removeClass("lg-hide-download");
$download.attr(
"href",
currentGalleryItem.downloadUrl || currentGalleryItem.src
);
if (currentGalleryItem.download) {
$download.attr("download", currentGalleryItem.download);
}
}
}
}
makeSlideAnimation(direction, currentSlideItem, previousSlideItem) {
if (this.lGalleryOn) {
previousSlideItem.addClass("lg-slide-progress");
}
setTimeout(
() => {
this.outer.addClass("lg-no-trans");
this.outer.find(".lg-item").removeClass("lg-prev-slide lg-next-slide");
if (direction === "prev") {
currentSlideItem.addClass("lg-prev-slide");
previousSlideItem.addClass("lg-next-slide");
} else {
currentSlideItem.addClass("lg-next-slide");
previousSlideItem.addClass("lg-prev-slide");
}
setTimeout(() => {
this.outer.find(".lg-item").removeClass("lg-current");
currentSlideItem.addClass("lg-current");
this.outer.removeClass("lg-no-trans");
}, 50);
},
this.lGalleryOn ? this.settings.slideDelay : 0
);
}
/**
* Goto a specific slide.
* @param {Number} index - index of the slide
* @param {Boolean} fromTouch - true if slide function called via touch event or mouse drag
* @param {Boolean} fromThumb - true if slide function called via thumbnail click
* @param {String} direction - Direction of the slide(next/prev)
* @category lGPublicMethods
* @example
* const plugin = lightGallery();
* // to go to 3rd slide
* plugin.slide(2);
*
*/
slide(index, fromTouch, fromThumb, direction) {
const prevIndex = this.getPreviousSlideIndex();
this.currentItemsInDom = this.organizeSlideItems(index, prevIndex);
if (this.lGalleryOn && prevIndex === index) {
return;
}
const numberOfGalleryItems = this.galleryItems.length;
if (!this.lgBusy) {
if (this.settings.counter) {
this.updateCurrentCounter(index);
}
this.announceSlide(index);
const currentSlideItem = this.getSlideItem(index);
const previousSlideItem = this.getSlideItem(prevIndex);
const currentGalleryItem = this.galleryItems[index];
const videoInfo = currentGalleryItem.__slideVideoInfo;
this.outer.attr(
"data-lg-slide-type",
this.getSlideType(currentGalleryItem)
);
this.setDownloadValue(index);
this.updateCurrentImageSize(index);
if (videoInfo) {
const { top, bottom } = this.mediaContainerPosition;
const videoSize = utils.getSize(
this.items[index],
this.outer,
top + bottom,
videoInfo && this.settings.videoMaxSize,
this.galleryItems[index].lgSize
);
this.resizeVideoSlide(index, videoSize);
}
this.LGel.trigger(lGEvents.beforeSlide, {
prevIndex,
index,
fromTouch: !!fromTouch,
fromThumb: !!fromThumb
});
this.lgBusy = true;
clearTimeout(this.hideBarTimeout);
this.arrowDisable(index);
if (!direction) {
if (index < prevIndex) {
direction = "prev";
} else if (index > prevIndex) {
direction = "next";
}
}
if (!fromTouch) {
this.makeSlideAnimation(
direction,
currentSlideItem,
previousSlideItem
);
} else {
this.outer.find(".lg-item").removeClass("lg-prev-slide lg-current lg-next-slide");
let touchPrev;
let touchNext;
if (numberOfGalleryItems > 2) {
touchPrev = index - 1;
touchNext = index + 1;
if (index === 0 && prevIndex === numberOfGalleryItems - 1) {
touchNext = 0;
touchPrev = numberOfGalleryItems - 1;
} else if (index === numberOfGalleryItems - 1 && prevIndex === 0) {
touchNext = 0;
touchPrev = numberOfGalleryItems - 1;
}
} else {
touchPrev = 0;
touchNext = 1;
}
if (direction === "prev") {
this.getSlideItem(touchNext).addClass("lg-next-slide");
} else {
this.getSlideItem(touchPrev).addClass("lg-prev-slide");
}
currentSlideItem.addClass("lg-current");
}
if (!this.lGalleryOn) {
this.loadContent(index, true);
} else {
setTimeout(() => {
this.loadContent(index, true);
if (this.settings.appendSubHtmlTo !== ".lg-item") {
this.addHtml(index);
}
}, this.settings.speed + 50 + (fromTouch ? 0 : this.settings.slideDelay));
}
setTimeout(() => {
this.lgBusy = false;
previousSlideItem.removeClass("lg-slide-progress");
this.LGel.trigger(lGEvents.afterSlide, {
prevIndex,
index,
fromTouch,
fromThumb
});
}, (this.lGalleryOn ? this.settings.speed + 100 : 100) + (fromTouch ? 0 : this.settings.slideDelay));
}
this.index = index;
}
updateCurrentCounter(index) {
this.getElementById("lg-counter-current").html(index + 1 + "");
}
/**
* Plain-text caption of a slide for the aria-live announcer. Resolves
* the same sources addHtml uses (inline subHtml markup or a selector)
* and strips the markup down to readable text. Remote captions
* (subHtmlUrl) are skipped, announcing can't wait on a fetch.
*/
getSlideCaptionText(index) {
const currentGalleryItem = this.galleryItems[index];
if (!currentGalleryItem || currentGalleryItem.subHtmlUrl) {
return "";
}
let subHtml = currentGalleryItem.subHtml || "";
const firstLetter = subHtml.substring(0, 1);
if (firstLetter === "." || firstLetter === "#") {
try {
if (this.settings.subHtmlSelectorRelative && !this.settings.dynamic) {
subHtml = $LG(this.items).eq(index).find(subHtml).first().html();
} else {
subHtml = $LG(subHtml).first().html();
}
} catch (error) {
subHtml = "";
}
}
if (!subHtml) {
return "";
}
const container = document.createElement("div");
container.innerHTML = subHtml;
return container.textContent || "";
}
/**
* Update the polite live region with the shown slide's position and
* caption ("Image X of Y, caption"). The single announcement source,
* counter and caption bar are not live regions while this is enabled.
*/
announceSlide(index) {
if (!this.settings.ariaAnnouncements) {
return;
}
const announcer = this.getElementById("lg-announcer").get();
if (!announcer) {
return;
}
announcer.textContent = formatSlideAnnouncement({
template: this.settings.strings["slideAnnouncement"],
index: index + 1,
total: this.galleryItems.length,
caption: this.getSlideCaptionText(index)
});
}
updateCounterTotal() {
this.getElementById("lg-counter-all").html(
this.galleryItems.length + ""
);
}
getSlideType(item) {
if (item.__slideVideoInfo) {
return "video";
} else if (item.iframe) {
return "iframe";
} else {
return "image";
}
}
/** Stop a running release spring; visuals stay at the live frame. */
stopSlideSpring() {
if (this.cancelSlideSpring) {
this.cancelSlideSpring();
this.cancelSlideSpring = void 0;
}
}
touchMove(startCoords, endCoords, e) {
const distanceX = endCoords.pageX - startCoords.pageX;
const distanceY = endCoords.pageY - startCoords.pageY;
this.swipeSamples = pushVelocitySample(this.swipeSamples, {
x: endCoords.pageX,
y: endCoords.pageY,
t: Date.now()
});
this.swipeDirection = getSwipeAxis(
distanceX,
distanceY,
this.swipeDirection
);
if (!this.swipeDirection) {
return;
}
const $currentSlide = this.getSlideItem(this.index);
if (this.swipeDirection === "horizontal") {
e == null ? void 0 : e.preventDefault();
this.outer.addClass("lg-dragging");
const transforms = getHorizontalDragTransforms(
this.getEdgeDragDelta(distanceX),
$currentSlide.get().offsetWidth,
this.getDirection()
);
$currentSlide.css("transform", transforms.current);
this.outer.find(".lg-prev-slide").first().css("transform", transforms.prev);
this.outer.find(".lg-next-slide").first().css("transform", transforms.next);
} else if (this.swipeDirection === "vertical") {
if (this.settings.swipeToClose) {
e == null ? void 0 : e.preventDefault();
this.$container.addClass("lg-dragging-vertical");
const effects = getVerticalDragEffects(
distanceY,
window.innerWidth,
window.innerHeight
);
this.$backdrop.css("opacity", effects.backdropOpacity);
$currentSlide.css("transform", effects.transform);
if (effects.hideUi) {
this.outer.addClass("lg-hide-items").removeClass("lg-components-open");
}
}
}
}
/**
* The horizontal delta as rendered: rubber-banded when the drag
* points past the first/last slide with nothing there (loop counts).
*/
getEdgeDragDelta(distanceX) {
const count = this.galleryItems.length;
return getEdgeFrictionedDelta(
distanceX,
resolveSwipeTarget(
"prev",
this.index,
count,
this.settings.loop
) !== null,
resolveSwipeTarget(
"next",
this.index,
count,
this.settings.loop
) !== null,
this.getDirection()
);
}
/** Resolved reading direction ('auto' is resolved at init). */
getDirection() {
return this.settings.direction === "rtl" ? "rtl" : "ltr";
}
/**
* Spring the dragged slides back to rest, seeded with the release
* velocity. Returns false (no spring) for sub-pixel drags.
*/
springSlidesBack(deltaX, velocityX) {
if (Math.abs(deltaX) < 1) {
return false;
}
const $currentSlide = this.getSlideItem(this.index);
const $prev = this.outer.find(".lg-prev-slide").first();
const $next = this.outer.find(".lg-next-slide").first();
const width = $currentSlide.get().offsetWidth;
this.stopSlideSpring();
this.cancelSlideSpring = runSprings(
[{ from: deltaX, velocity: velocityX, target: 0 }],
([x]) => {
const transforms = getHorizontalDragTransforms(
x,
width,
this.getDirection()
);
$currentSlide.css("transform", transforms.current);
$prev.css("transform", transforms.prev);
$next.css("transform", transforms.next);
},
() => {
this.cancelSlideSpring = void 0;
this.endDragVisuals();
}
);
return true;
}
/**
* Carry the release velocity into a gesture navigation: navigate
* immediately (events, counter and busy-lock on time; fromTouch
* classes flip underneath, inline transforms keep the visuals),
* then spring the same drag geometry until the arriving slide lands
* at rest. Falls back to a snap-back when navigation declines
* (busy, or an edge without loop, the 2.x end animation plays).
*/
springSlideNavigation(verdict, deltaX, velocityX) {
const $current = this.getSlideItem(this.index);
const $prev = this.outer.find(".lg-prev-slide").first();
const $next = this.outer.find(".lg-next-slide").first();
const width = $current.get().offsetWidth;
if (!width) {
return false;
}
const prevIndex = this.index;
if (verdict === "next") {
this.goToNextSlide(true);
} else {
this.goToPrevSlide(true);
}
if (this.index === prevIndex) {
return this.springSlidesBack(deltaX, velocityX);
}
const target = (verdict === "next" ? -1 : 1) * (this.getDirection() === "rtl" ? -1 : 1) * (width * 115 / 110);
this.stopSlideSpring();
this.cancelSlideSpring = runSprings(
[{ from: deltaX, velocity: velocityX, target }],
([x]) => {
const transforms = getHorizontalDragTransforms(
x,
width,
this.getDirection()
);
$current.css("transform", transforms.current);
$prev.css("transform", transforms.prev);
$next.css("transform", transforms.next);
},
() => {
this.cancelSlideSpring = void 0;
this.endDragVisuals();
}
);
return true;
}
/**
* Hand the slides back to CSS after a gesture. The inline transforms
* go first, while lg-dragging still pins transition-duration to 0s,
* so the snap from the released position to the slide's resting
* place lands in a single frame. Dropping the class first animates
* that snap instead, and the outgoing slide is seen travelling back
* toward the centre while its fade is still running.
*/
endDragVisuals() {
this.outer.find(".lg-item").removeAttr("style");
if (this.settings.mode !== "lg-slide") {
this.outer.removeClass("lg-slide");
}
void this.outer.get().offsetHeight;
this.outer.removeClass("lg-dragging");
}
/**
* Spring a non-closing vertical drag back to rest, slide transform
* and backdrop opacity together, seeded with the release velocity.
*/
springVerticalRestore(deltaY, velocityY) {
if (Math.abs(deltaY) < 1) {
return false;
}
const $currentSlide = this.getSlideItem(this.index);
this.stopSlideSpring();
this.$container.addClass("lg-dragging-vertical");
this.cancelSlideSpring = runSprings(
[{ from: deltaY, velocity: velocityY, target: 0 }],
([y]) => {
const effects = getVerticalDragEffects(
y,
window.innerWidth,
window.innerHeight
);
this.$backdrop.css("opacity", effects.backdropOpacity);
$currentSlide.css("transform", effects.transform);
},
() => {
this.cancelSlideSpring = void 0;
this.$container.removeClass("lg-dragging-vertical");
this.endDragVisuals();
this.$backdrop.css("opacity", 1);
}
);
return true;
}
touchEnd(endCoords, startCoords, event) {
const releaseVelocity = getWindowedVelocity(
this.swipeSamples,
Date.now()
);
if (this.settings.mode !== "lg-slide") {
this.outer.addClass("lg-slide");
}
setTimeout(() => {
this.$container.removeClass("lg-dragging-vertical");
this.outer.removeClass("lg-hide-items").addClass("lg-components-open");
let triggerClick = true;
let springing = false;
if (this.swipeDirection === "horizontal") {
const verdict = getSwipeReleaseVerdict({
deltaX: endCoords.pageX - startCoords.pageX,
velocityX: releaseVelocity.x,
threshold: this.settings.swipeThreshold,
flickVelocity: this.settings.flickVelocity,
viewportWidth: this.outer.get().offsetWidth,
direction: this.getDirection()
});
const renderedDeltaX = this.getEdgeDragDelta(
endCoords.pageX - startCoords.pageX
);
if (verdict === "next" || verdict === "prev") {
triggerClick = false;
springing = this.springSlideNavigation(
verdict,
renderedDeltaX,
releaseVelocity.x
);
} else {
springing = this.springSlidesBack(
renderedDeltaX,
releaseVelocity.x
);
}
} else if (this.swipeDirection === "vertical") {
if (shouldCloseOnVerticalDrag(
endCoords.pageY - startCoords.pageY,
releaseVelocity.y,
window.innerHeight,
{
closable: this.settings.closable,
swipeToClose: this.settings.swipeToClose
}
)) {
this.closeGallery();
return;
}
if (this.settings.swipeToClose) {
springing = this.springVerticalRestore(
endCoords.pageY - startCoords.pageY,
releaseVelocity.y
);
}
}
if (!springing) {
this.endDragVisuals();
this.$backdrop.css("opacity", 1);
}
if (triggerClick && Math.abs(endCoords.pageX - startCoords.pageX) < 5) {
const target = $LG(event.target);
if (this.isPosterElement(target)) {
this.LGel.trigger(lGEvents.posterClick);
}
}
this.swipeDirection = void 0;
});
setTimeout(() => {
if (!this.outer.hasClass("lg-dragging") && this.settings.mode !== "lg-slide") {
this.outer.removeClass("lg-slide");
}
}, this.settings.speed + 100);
}
enableSwipe() {
let startCoords = {};
let endCoords = {};
let isMoved = false;
let isSwiping = false;
if (this.settings.enableSwipe) {
this.$inner.on("touchstart.lg", (e) => {
this.dragOrSwipeEnabled = true;
const $item = this.getSlideItem(this.index);
if (($LG(e.target).hasClass("lg-item") || $item.get().contains(e.target)) && !this.outer.hasClass("lg-zoomed") && !this.lgBusy && e.touches.length === 1) {
isSwiping = true;
this.touchAction = "swipe";
isMoved = false;
endCoords = {};
this.stopSlideSpring();
this.manageSwipeClass();
this.swipeSamples = [
{
x: e.touches[0].pageX,
y: e.touches[0].pageY,
t: Date.now()
}
];
startCoords = {
pageX: e.touches[0].pageX,
pageY: e.touches[0].pageY
};
}
});
this.$inner.on("touchmove.lg", (e) => {
if (isSwiping && this.touchAction === "swipe" && e.touches.length === 1) {
endCoords = {
pageX: e.touches[0].pageX,
pageY: e.touches[0].pageY
};
this.touchMove(startCoords, endCoords, e);
isMoved = true;
}
});
this.$inner.on("touchend.lg", (event) => {
if (this.touchAction === "swipe") {
if (isMoved) {
isMoved = false;
this.touchEnd(endCoords, startCoords, event);
} else if (isSwiping) {
const target = $LG(event.target);
if (this.isPosterElement(target)) {
this.LGel.trigger(lGEvents.posterClick);
}
}
this.touchAction = void 0;
isSwiping = false;
}
});
}
}
enableDrag() {
let startCoords = {};
let endCoords = {};
let isDraging = false;
let isMoved = false;
if (this.settings.enableDrag) {
this.outer.on("mousedown.lg", (e) => {
this.dragOrSwipeEnabled = true;
const $item = this.getSlideItem(this.index);
if ($LG(e.target).hasClass("lg-item") || $item.get().contains(e.target)) {
if (!this.outer.hasClass("lg-zoomed") && !this.lgBusy) {
e.preventDefault();
this.stopSlideSpring();
this.manageSwipeClass();
this.swipeSamples = [
{ x: e.pageX, y: e.pageY, t: Date.now() }
];
startCoords = {
pageX: e.pageX,
pageY: e.pageY
};
isDraging = true;
this.outer.get().scrollLeft += 1;
this.outer.get().scrollLeft -= 1;
this.outer.removeClass("lg-grab").addClass("lg-grabbing");
this.LGel.trigger(lGEvents.dragStart);
}
}
});
$LG(window).on(`mousemove.lg.global${this.lgId}`, (e) => {
if (isDraging && this.lgOpened) {
isMoved = true;
endCoords = {
pageX: e.pageX,
pageY: e.pageY
};
this.touchMove(startCoords, endCoords);
this.LGel.trigger(lGEvents.dragMove);
}
});
$LG(window).on(`mouseup.lg.global${this.lgId}`, (event) => {
if (!this.lgOpened) {
return;
}
const target = $LG(event.target);
if (isMoved) {
isMoved = false;
this.touchEnd(endCoords, startCoords, event);
this.LGel.trigger(lGEvents.dragEnd);
} else if (this.isPosterElement(target)) {
this.LGel.trigger(lGEvents.posterClick);
}
if (isDraging) {
isDraging = false;
this.outer.removeClass("lg-grabbing").addClass("lg-grab");
}
});
}
}
triggerPosterClick() {
this.$inner.on("click.lg", (event) => {
if (!this.dragOrSwipeEnabled && this.isPosterElement($LG(event.target))) {
this.LGel.trigger(lGEvents.posterClick);
}
});
}
manageSwipeClass() {
let _touchNext = this.index + 1;
let _touchPrev = this.index - 1;
if (this.settings.loop && this.galleryItems.length > 2) {
if (this.index === 0) {
_touchPrev = this.galleryItems.length - 1;
} else if (this.index === this.galleryItems.length - 1) {
_touchNext = 0;
}
}
this.outer.find(".lg-item").removeClass("lg-next-slide lg-prev-slide");
if (_touchPrev > -1) {
this.getSlideItem(_touchPrev).addClass("lg-prev-slide");
}
this.getSlideItem(_touchNext).addClass("lg-next-slide");
}
/**
* Go to next slide
* @param {Boolean} fromTouch - true if slide function called via touch event
* @category lGPublicMethods
* @example
* const plugin = lightGallery();
* plugin.goToNextSlide();
* @see <a href="/demos/methods/">Demo</a>
*/
goToNextSlide(fromTouch) {
let _loop = this.settings.loop;
if (fromTouch && this.galleryItems.length < 3) {
_loop = false;
}
if (!this.lgBusy) {
if (this.index + 1 < this.galleryItems.length) {
this.index++;
this.LGel.trigger(lGEvents.beforeNextSlide, {
index: this.index
});
this.slide(this.index, !!fromTouch, false, "next");
} else {
if (_loop) {
this.index = 0;
this.LGel.trigger(lGEvents.beforeNextSlide, {
index: this.index
});
this.slide(this.index, !!fromTouch, false, "next");
} else if (this.settings.slideEndAnimation && !fromTouch) {
this.outer.addClass("lg-right-end");
setTimeout(() => {
this.outer.removeClass("lg-right-end");
}, 400);
}
}
}
}
/**
* Go to previous slide
* @param {Boolean} fromTouch - true if slide function called via touch event
* @category lGPublicMethods
* @example
* const plugin = lightGallery({});
* plugin.goToPrevSlide();
* @see <a href="/demos/methods/">Demo</a>
*
*/
goToPrevSlide(fromTouch) {
let _loop = this.settings.loop;
if (fromTouch && this.galleryItems.length < 3) {
_loop = false;
}
if (!this.lgBusy) {
if (this.index > 0) {
this.index--;
this.LGel.trigger(lGEvents.beforePrevSlide, {
index: this.index,
fromTouch
});
this.slide(this.index, !!fromTouch, false, "prev");
} else {
if (_loop) {
this.index = this.galleryItems.length - 1;
this.LGel.trigger(lGEvents.beforePrevSlide, {
index: this.index,
fromTouch
});
this.slide(this.index, !!fromTouch, false, "prev");
} else if (this.settings.slideEndAnimation && !fromTouch) {
this.outer.addClass("lg-left-end");
setTimeout(() => {
this.outer.removeClass("lg-left-end");
}, 400);
}
}
}
}
keyPress() {
$LG(window).on(`keydown.lg.global${this.lgId}`, (e) => {
if (this.lgOpened && this.settings.escKey === true && e.keyCode === 27) {
e.preventDefault();
if (this.settings.allowMediaOverlap && this.outer.hasClass("lg-can-toggle") && this.outer.hasClass("lg-components-open")) {
this.outer.removeClass("lg-components-open");
} else {
this.closeGallery();
}
}
if (this.lgOpened && this.galleryItems.length > 1) {
const rtl = this.getDirection() === "rtl";
if (e.keyCode === 37) {
e.preventDefault();
if (rtl) {
this.goToNextSlide();
} else {
this.goToPrevSlide();
}
}
if (e.keyCode === 39) {
e.preventDefault();
if (rtl) {
this.goToPrevSlide();
} else {
this.goToNextSlide();
}
}
}
});
}
arrow() {
this.getElementById("lg-prev").on("click.lg", () => {
this.goToPrevSlide();
});
this.getElementById("lg-next").on("click.lg", () => {
this.goToNextSlide();
});
}
arrowDisable(index) {
if (!this.settings.loop && this.settings.hideControlOnEnd) {
const $prev = this.getElementById("lg-prev");
const $next = this.getElementById("lg-next");
if (index + 1 === this.galleryItems.length) {
$next.attr("disabled", "disabled").addClass("disabled");
} else {
$next.removeAttr("disabled").removeClass("disabled");
}
if (index === 0) {
$prev.attr("disabled", "disabled").addClass("disabled");
} else {
$prev.removeAttr("disabled").removeClass("disabled");
}
}
}
setTranslate($el, xValue, yValue, scaleX = 1, scaleY = 1) {
$el.css(
"transform",
"translate3d(" + xValue + "px, " + yValue + "px, 0px) scale3d(" + scaleX + ", " + scaleY + ", 1)"
);
}
mousewheel() {
let lastCall = 0;
this.outer.on("wheel.lg", (e) => {
if (!e.deltaY || this.galleryItems.length < 2) {
return;
}
e.preventDefault();
const now = (/* @__PURE__ */ new Date()).getTime();
if (now - lastCall < 1e3) {
return;
}
lastCall = now;
if (e.deltaY > 0) {
this.goToNextSlide();
} else if (e.deltaY < 0) {
this.goToPrevSlide();
}
});
}
isSlideElement(target) {
return target.hasClass("lg-outer") || target.hasClass("lg-item") || target.hasClass("lg-img-wrap") || target.hasClass("lg-img-rotate");
}
isPosterElement(target) {
const playButton = this.getSlideItem(this.index).find(".lg-video-play-button").get();
return target.hasClass("lg-video-poster") || target.hasClass("lg-video-play-button") || playButton && playButton.contains(target.get());
}
/**
* Bind the inline gallery's maximize button: each click toggles the
* gallery between its container and the full viewport. Called once
* during initialization.
*/
toggleMaximize() {
this.getElementById("lg-maximize").on("click.lg", () => {
this.$container.toggleClass("lg-inline");
this.refreshOnResize();
});
}
invalidateItems() {
for (let index = 0; index < this.items.length; index++) {
const element = this.items[index];
const $element = $LG(element);
$element.off(`click.lgcustom-item-${$element.attr("data-lg-id")}`);
}
}
trapFocus() {
this.$container.get().focus({
preventScroll: true
});
$LG(window).on(`keydown.lg.global${this.lgId}`, (e) => {
if (!this.lgOpened) {
return;
}
const isTabPressed = e.key === "Tab" || e.keyCode === 9;
if (!isTabPressed) {
return;
}
const focusableEls = utils.getFocusableElements(
this.$container.get()
);
const firstFocusableEl = focusableEls[0];
const lastFocusableEl = focusableEls[focusableEls.length - 1];
if (e.shiftKey) {
if (document.activeElement === firstFocusableEl) {
lastFocusableEl.focus();
e.preventDefault();
}
} else {
if (document.activeElement === lastFocusableEl) {
firstFocusableEl.focus();
e.preventDefault();
}
}
});
}
manageCloseGallery() {
if (!this.settings.closable) return;
let mousedown = false;
this.getElementById("lg-close").on("click.lg", () => {
this.closeGallery();
});
if (this.settings.closeOnTap) {
this.outer.on("mousedown.lg", (e) => {
const target = $LG(e.target);
if (this.isSlideElement(target)) {
mousedown = true;
} else {
mousedown = false;
}
});
this.outer.on("mousemove.lg", () => {
mousedown = false;
});
this.outer.on("mouseup.lg", (e) => {
const target = $LG(e.target);
if (this.isSlideElement(target) && mousedown) {
if (!this.outer.hasClass("lg-dragging")) {
this.closeGallery();
}
}
});
}
}
/**
* Close lightGallery if it is opened.
*
* @description If closable is false in the settings, you need to pass true via closeGallery method to force close gallery
* @return returns the estimated time to close gallery completely including the close animation duration
* @category lGPublicMethods
* @example
* const plugin = lightGallery();
* plugin.closeGallery();
*
*/
closeGallery(force) {
if (!this.lgOpened || !this.settings.closable && !force) {
return 0;
}
this.LGel.trigger(lGEvents.beforeClose);
if (this.settings.resetScrollPosition && !this.settings.hideScrollbar) {
$LG(window).scrollTop(this.prevScrollTop);
}
const currentItem = this.items[this.index];
let transform;
if (this.zoomFromOrigin && currentItem) {
const { top, bottom } = this.mediaContainerPosition;
const { __slideVideoInfo, poster } = this.galleryItems[this.index];
const imageSize = utils.getSize(
currentItem,
this.outer,
top + bottom,
__slideVideoInfo && poster && this.settings.videoMaxSize
);
transform = this.getOriginTransform(currentItem, imageSize);
}
if (!transform) {
transform = getCenterCloseTransform();
this.outer.addClass("lg-close-to-center");
}
if (transform) {
this.outer.addClass("lg-closing lg-zoom-from-image");
this.getSlideItem(this.index).addClass("lg-start-end-progress").css(
"transition-duration",
this.settings.startAnimationDuration + "ms"
).css("transform", transform);
} else {
this.outer.addClass("lg-hide-items");
this.outer.removeClass("lg-zoom-from-image");
}
this.destroyModules();
this.lGalleryOn = false;
this.isDummyImageRemoved = false;
this.zoomFromOrigin = this.settings.zoomFromOrigin;
clearTimeout(this.hideBarTimeout);
this.hideBarTimeout = false;
$LG("html").removeClass("lg-on");
this.outer.removeClass("lg-visible lg-components-open");
this.$backdrop.removeClass("in").css("opacity", 0);
const removeTimeout = transform ? Math.max(
this.settings.startAnimationDuration,
this.settings.backdropDuration
) : this.settings.backdropDuration;
this.$container.removeClass("lg-show-in");
setTimeout(() => {
if (transform) {
this.outer.removeClass("lg-zoom-from-image");
}
this.$container.removeClass("lg-show");
this.resetScrollBar();
this.$backdrop.removeAttr("style").css(
"transition-duration",
this.settings.backdropDuration + "ms"
);
this.outer.removeClass(
`lg-closing lg-close-to-center ${this.settings.startClass}`
);
this.getSlideItem(this.index).removeClass("lg-start-end-progress");
this.$inner.empty();
const announcer = this.getElementById("lg-announcer").get();
if (announcer) {
announcer.textContent = "";
}
if (this.lgOpened) {
this.LGel.trigger(lGEvents.afterClose, {
instance: this
});
}
if (this.$container.get()) {
this.$container.get().blur();
}
if (this.prevActiveElement && this.prevActiveElement.isConnected) {
this.prevActiveElement.focus({ preventScroll: true });
}
this.prevActiveElement = void 0;
this.lgOpened = false;
}, removeTimeout + 100);
return removeTimeout + 100;
}
initModules() {
this.plugins.forEach((module2) => {
try {
module2.init();
} catch (err) {
console.warn(
`lightGallery:- make sure lightGallery module is properly initiated. See https://www.lightgalleryjs.com/docs/methods/`
);
}
});
}
destroyModules(destroy) {
this.plugins.forEach((module2) => {
try {
if (destroy) {
module2.destroy();
} else {
module2.closeGallery && module2.closeGallery();
}
} catch (err) {
console.warn(
`lightGallery:- make sure lightGallery module is properly destroyed. See https://www.lightgalleryjs.com/docs/methods/`
);
}
});
}
/**
* Refresh lightGallery with new set of children.
*
* @description This is useful to update the gallery when the child elements are changed without calling destroy method.
*
* If you are using dynamic mode, you can pass the modified array of dynamicEl as the first parameter to refresh the dynamic gallery
* @see <a href="/demos/dynamic-mode/">Demo</a>
* @category lGPublicMethods
* @example
* const plugin = lightGallery();
* // Delete or add children, then call
* plugin.refresh();
*
*/
refresh(galleryItems) {
if (!this.settings.dynamic) {
this.invalidateItems();
}
if (galleryItems) {
this.galleryItems = galleryItems;
} else {
this.galleryItems = this.getItems();
}
this.updateControls();
this.openGalleryOnItemClick();
this.LGel.trigger(lGEvents.updateSlides);
}
updateControls() {
this.addSlideVideoInfo(this.galleryItems);
this.updateCounterTotal();
this.manageSingleSlideClassName();
}
destroyGallery() {
var _a;
this.destroyModules(true);
if (!this.settings.dynamic) {
this.invalidateItems();
}
$LG(window).off(`.lg.global${this.lgId}`);
this.LGel.off(".lg");
(_a = this.toolbarOverflow) == null ? void 0 : _a.destroy();
this.toolbarOverflow = void 0;
this.$container.remove();
}
/**
* Destroy lightGallery.
* Destroy lightGallery and its plugin instances completely
*
* @description This method also calls closeGallery function internally. Returns the time it takes to completely close and destroy the instance.
* In case if you want to re-initialize lightGallery right after destroying it, initialize it only once the destroy process is completed.
* You can use refresh method most of the time.
* @category lGPublicMethods
* @example
* const plugin = lightGallery();
* plugin.destroy();
*
*/
destroy() {
const closeTimeout = this.closeGallery(true);
if (closeTimeout) {
setTimeout(this.destroyGallery.bind(this), closeTimeout);
} else {
this.destroyGallery();
}
return closeTimeout;
}
}
function lightGallery(el, options) {
return new LightGallery(el, options);
}
lightGallery.setLicenseKey = setLicenseKey;
return lightGallery;
});
//# sourceMappingURL=lightgallery.umd.js.map