PageSourceSearch

https://liveatthecrossings.com/assets/js/simpleLightboxCustom.js

js liveatthecrossings.com collected 2026-10-10 01:48:13 UTC 12,866 bytes, 521 lines download raw bytes

vendor: 7,385 bytes, lines 1-311
1(function (root, factory) {
2	if (typeof define === "function" && define.amd) {
3		define([], factory);
4	} else if (typeof module === "object" && module.exports) {
5		module.exports = factory();
6	} else {
7		root.SimpleLightbox = factory();
8	}
9})(this, function () {
10	function assign(target) {
11		for (var i = 1; i < arguments.length; i++) {
12			var obj = arguments[i];
13
14			if (obj) {
15				for (var key in obj) {
16					obj.hasOwnProperty(key) && (target[key] = obj[key]);
17				}
18			}
19		}
20
21		return target;
22	}
23
24	function addClass(element, className) {
25		if (element && className) {
26			element.className += " " + className;
27		}
28	}
29
30	function removeClass(element, className) {
31		if (element && className) {
32			element.className = element.className
33				.replace(new RegExp("(\\s|^)" + className + "(\\s|$)"), " ")
34				.trim();
35		}
36	}
37
38	function parseHtml(html) {
39		var div = document.createElement("div");
40		div.innerHTML = html.trim();
41
42		return div.childNodes[0];
43	}
44
45	function matches(el, selector) {
46		return (el.matches || el.matchesSelector || el.msMatchesSelector).call(
47			el,
48			selector
49		);
50	}
51
52	function getWindowHeight() {
53		return "innerHeight" in window
54			? window.innerHeight
55			: document.documentElement.offsetHeight;
56	}
57
58	function SimpleLightbox(options) {
59		this.init.apply(this, arguments);
60	}
61
62	SimpleLightbox.defaults = {
63		// add custom classes to lightbox elements
64		elementClass: "",
65		elementLoadingClass: "slbLoading",
66		htmlClass: "slbActive",
67		closeBtnClass: "",
68		nextBtnClass: "",
69		prevBtnClass: "",
70		loadingTextClass: "",
71
72		// customize / localize controls captions
73		closeBtnCaption: "Close",
74		nextBtnCaption: "Next",
75		prevBtnCaption: "Previous",
76		loadingCaption: "Loading...",
77
78		bindToItems: true, // set click event handler to trigger lightbox on provided $items
79		closeOnOverlayClick: true,
80		closeOnEscapeKey: true,
81		nextOnImageClick: true,
82		showCaptions: true,
83
84		captionAttribute: "title", // choose data source for library to glean image caption from
85		urlAttribute: "href", // where to expect large image
86
87		startAt: 0, // start gallery at custom index
88		loadingTimeout: 100, // time after loading element will appear
89
90		appendTarget: "body", // append elsewhere if needed
91
92		beforeSetContent: null, // convenient hooks for extending library behavoiur
93		beforeClose: null,
94		afterClose: null,
95		beforeDestroy: null,
96		afterDestroy: null,
97
98		videoRegex: new RegExp(/youtube.com|vimeo.com/), // regex which tests load url for iframe content
99	};
100
101	assign(SimpleLightbox.prototype, {
102		init: function (options) {
103			options = this.options = assign({}, SimpleLightbox.defaults, options);
104
105			var self = this;
106			var elements;
107
108			if (options.$items) {
109				elements = options.$items.get();
110			}
111
112			if (options.elements) {
113				elements = [].slice.call(
114					typeof options.elements === "string"
115						? document.querySelectorAll(options.elements)
116						: options.elements
117				);
118			}
119
120			this.eventRegistry = { lightbox: [], thumbnails: [] };
121			this.items = [];
122			this.captions = [];
123
124			if (elements) {
125				elements.forEach(function (element, index) {
126					self.items.push(element.getAttribute(options.urlAttribute));
127					self.captions.push(element.getAttribute(options.captionAttribute));
128
129					if (options.bindToItems) {
130						self.addEvent(
131							element,
132							"click",
133							function (e) {
134								e.preventDefault();
135								self.showPosition(index);
136							},
137							"thumbnails"
138						);
139					}
140				});
141			}
142
143			if (options.items) {
144				this.items = options.items;
145			}
146
147			if (options.captions) {
148				this.captions = options.captions;
149			}
150		},
151
152		addEvent: function (element, eventName, callback, scope) {
153			this.eventRegistry[scope || "lightbox"].push({
154				element: element,
155				eventName: eventName,
156				callback: callback,
157			});
158
159			element.addEventListener(eventName, callback);
160
161			return this;
162		},
163
164		removeEvents: function (scope) {
165			this.eventRegistry[scope].forEach(function (item) {
166				item.element.removeEventListener(item.eventName, item.callback);
167			});
168
169			this.eventRegistry[scope] = [];
170
171			return this;
172		},
173
174		next: function () {
175			this.showPosition(this.currentPosition + 1);
176		},
177
178		prev: function () {
179			this.showPosition(this.currentPosition - 1);
180		},
181
182		normalizePosition: function (position) {
183			if (position >= this.items.length) {
184				position = 0;
185			} else if (position < 0) {
186				position = this.items.length - 1;
187			}
188
189			return position;
190		},
191
192		showPosition: function (position) {
193			var newPosition = this.normalizePosition(position);
194
195			if (typeof this.currentPosition !== "undefined") {
196				this.direction = newPosition > this.currentPosition ? "next" : "prev";
197			}
198
199			this.currentPosition = newPosition;
200
201			// Return a promise
202			return new Promise((resolve, reject) => {
203				try {
204					this.setupLightboxHtml()
205						.prepareItem(this.currentPosition, this.setContent)
206						.show();
207
208					// Use requestAnimationFrame to resolve the promise after the next frame
209					requestAnimationFrame(() => {
210						resolve(); // Resolve promise
211					});
212				} catch (error) {
213					reject(error); // Reject promise if an error occurs
214				}
215			});
216		},
217
218		loading: function (on) {
219			var self = this;
220			var options = this.options;
221
222			if (on) {
223				this.loadingTimeout = setTimeout(function () {
224					addClass(self.$el, options.elementLoadingClass);
225
226					self.$content.innerHTML =
227						'<p class="slbLoadingText ' +
228						options.loadingTextClass +
229						'">' +
230						options.loadingCaption +
231						"</p>";
232					self.show();
233				}, options.loadingTimeout);
234			} else {
235				removeClass(this.$el, options.elementLoadingClass);
236				clearTimeout(this.loadingTimeout);
237			}
238		},
239
240		prepareItem: function (position, callback) {
241			var self = this;
242			var url = this.items[position];
243
244			this.loading(true);
245
246			if (this.options.videoRegex.test(url)) {
247				callback.call(
248					self,
249					parseHtml(
250						'<div class="slbIframeCont"><iframe class="slbIframe" frameborder="0" allowfullscreen src="' +
251							url +
252							'"></iframe></div>'
253					)
254				);
255			} else {
256				var $imageCont = parseHtml(
257					'<div class="slbImageWrap"><img class="slbImage" src="' +
258						url +
259						'" /></div>'
260				);
261
262				this.$currentImage = $imageCont.querySelector(".slbImage");
263
264				if (this.options.showCaptions && this.captions[position]) {
265					$imageCont.appendChild(
266						parseHtml(
267							'<div class="slbCaption">' + this.captions[position] + "</div>"
268						)
269					);
270				}
271
272				this.loadImage(url, function () {
273					self.setImageDimensions();
274
275					callback.call(self, $imageCont);
276
277					self.loadImage(
278						self.items[self.normalizePosition(self.currentPosition + 1)]
279					);
280				});
281			}
282
283			return this;
284		},
285
286		loadImage: function (url, callback) {
287			if (!this.options.videoRegex.test(url)) {
288				var image = new Image();
289				callback && (image.onload = callback);
290				image.src = url;
291			}
292		},
293
294		setupLightboxHtml: function () {
295			var o = this.options;
296
297			if (!this.$el) {
298				this.$el = parseHtml(
299					'<div class="slbElement ' +
300						o.elementClass +
301						'">' +
302						'<div class="slbOverlay"></div>' +
303						'<div class="slbWrapOuter">' +
304						'<div class="slbWrap">' +
305						'<div class="slbContentOuter">' +
306						'<div class="slbContent"></div>' +
307						'<button type="button" title="' +
308						o.closeBtnCaption +
309						'" class="slbCloseBtn ' +
310						o.closeBtnClass +
311						'">
311×</button>' +
312						(this.items.length > 1
313							? '<div class="slbArrows">' +
314							  '<button type="button" title="' +
315							  o.prevBtnCaption +
316							  '" class="prev slbArrow' +
317							  o.prevBtnClass +
318							  '">' +
319							  o.prevBtnCaption +
320							  "</button>" +
321							  '<button type="button" title="' +
322							  o.nextBtnCaption +
323							  '" class="next slbArrow' +
324							  o.nextBtnClass +
325							  '">' +
326							  o.nextBtnCaption +
327							  "</button>" +
328							  "</div>"
329							: "") +
330						"</div>" +
331						"</div>" +
332						"</div>" +
333						"</div>"
334				);
335
336				this.$content = this.$el.querySelector(".slbContent");
337			}
338
339			this.$content.innerHTML = "";
340
341			return this;
342		},
343
344		show: function () {
345			if (!this.modalInDom) {
346				document.querySelector(this.options.appendTarget).appendChild(this.$el);
347				addClass(document.documentElement, this.options.htmlClass);
348				this.setupLightboxEvents();
349				this.modalInDom = true;
350				this.lastFocusedElement = document.activeElement;
351			}
352
353			return this;
354		},
355
356		setContent: function (content) {
357			var $content = typeof content === "string" ? parseHtml(content) : content;
358			this.loading(false);
359
360			this.setupLightboxHtml();
361
362			removeClass(this.$content, "slbDirectionNext");
363			removeClass(this.$content, "slbDirectionPrev");
364
365			if (this.direction) {
366				addClass(
367					this.$content,
368					this.direction === "next" ? "slbDirectionNext" : "slbDirectionPrev"
369				);
370			}
371
372			if (this.options.beforeSetContent) {
373				this.options.beforeSetContent($content, this);
374			}
375
376			this.$content.appendChild($content);
377
378			return this;
379		},
380
381		setImageDimensions: function () {
382			if (this.$currentImage) {
383				this.$currentImage.style.maxHeight = getWindowHeight() + "px";
384			}
385		},
386
387		setupLightboxEvents: function () {
388			var self = this;
389
390			if (this.eventRegistry.lightbox.length) {
391				return this;
392			}
393
394			this.addEvent(this.$el, "click", function (e) {
395				var $target = e.target;
396
397				if (
398					matches($target, ".slbCloseBtn") ||
399					(self.options.closeOnOverlayClick && matches($target, ".slbWrap"))
400				) {
401					self.close();
402				} else if (matches($target, ".slbArrow")) {
403					matches($target, ".next") ? self.next() : self.prev();
404				} else if (
405					self.options.nextOnImageClick &&
406					self.items.length > 1 &&
407					matches($target, ".slbImage")
408				) {
409					self.next();
410				}
411			})
412				.addEvent(document, "keyup", function (e) {
413					self.options.closeOnEscapeKey && e.keyCode === 27 && self.close();
414
415					if (self.items.length > 1) {
416						if (e.keyCode === 39 || e.keyCode === 68) {
417							self.next();
418						}
419						if (e.keyCode === 37 || e.keyCode === 65) {
420							self.prev();
421						}
422					}
423				})
424				.addEvent(window, "resize", function () {
425					self.setImageDimensions();
426				});
427
428			// Focus trapping logic
429			this.addEvent(document, "keydown", function (e) {
430				var isTabPressed = e.key === "Tab" || e.keyCode === 9;
431				if (!isTabPressed) {
432					return;
433				}
434				var focusableElements = self.$el.querySelectorAll(
435					'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
436				);
437				var firstFocusableElement = focusableElements[0]; // First focusable element
438				var lastFocusableElement =
439					focusableElements[focusableElements.length - 1]; // Last focusable element
440				if (![...focusableElements].includes(document.activeElement)) {
441					e.preventDefault();
442					firstFocusableElement.focus(); // Focus the first element
443				}
444				if (e.shiftKey) {
445					// If Shift + Tab is pressed
446					if (document.activeElement === firstFocusableElement) {
447						e.preventDefault();
448						lastFocusableElement.focus(); // Focus the last element
449					}
450				} else {
451					// If Tab is pressed
452					if (document.activeElement === lastFocusableElement) {
453						e.preventDefault();
454						firstFocusableElement.focus(); // Focus the first element
455					}
456				}
457			});
458
459			return this;
460		},
461
462		close: function () {
463			if (this.modalInDom) {
464				this.runHook("beforeClose");
465				this.removeEvents("lightbox");
466				this.$el && this.$el.parentNode.removeChild(this.$el);
467				removeClass(document.documentElement, this.options.htmlClass);
468				this.modalInDom = false;
469				this.lastFocusedElement && this.lastFocusedElement.focus();
470				this.lastFocusedElement = null;
471				this.runHook("afterClose");
472			}
473
474			this.direction = undefined;
475			this.currentPosition = this.options.startAt;
476		},
477
478		destroy: function () {
479			this.close();
480			this.runHook("beforeDestroy");
481			this.removeEvents("thumbnails");
482			this.runHook("afterDestroy");
483		},
484
485		runHook: function (name) {
486			this.options[name] && this.options[name](this);
487		},
488	});
489
490	SimpleLightbox.open = function (options) {
491		var instance = new SimpleLightbox(options);
492
493		return options.content
494			? instance.setContent(options.content).show()
495			: instance.showPosition(instance.options.startAt);
496	};
497
498	SimpleLightbox.registerAsJqueryPlugin = function ($) {
499		$.fn.simpleLightbox = function (options) {
500			var lightboxInstance;
501			var $items = this;
502
503			return this.each(function () {
504				if (!$.data(this, "simpleLightbox")) {
505					lightboxInstance =
506						lightboxInstance ||
507						new SimpleLightbox($.extend({}, options, { $items: $items }));
508					$.data(this, "simpleLightbox", lightboxInstance);
509				}
510			});
511		};
512
513		$.SimpleLightbox = SimpleLightbox;
514	};
515
516	if (typeof window !== "undefined" && window.jQuery) {
517		SimpleLightbox.registerAsJqueryPlugin(window.jQuery);
518	}
519
520	return SimpleLightbox;
521});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.