PageSourceSearch

https://bauroc.lv/themes/aeroc/library/ama/ama-lightbox.js?ver=1784878742

js bauroc.lv collected 2026-10-09 18:13:42 UTC 8,663 bytes, 365 lines download raw bytes

1/**
2 * AMA Lightbox
3 * ---------------------------------------------------------------------------
4 * Zero-dependency lightbox with no per-image markup requirements.
5 *
6 * Trigger rule: an <a> whose href points at an image file, wrapping a single
7 * <img> directly, located inside one of CONFIG.scopes. That is exactly what
8 * the block editor emits for "Link to → Media file", so editors get lightboxes
9 * without touching anything.
10 *
11 * Grouping: all qualifying links inside the same scope element form a gallery
12 * (arrows, keyboard, swipe). Override with data-lightbox-group="name" to group
13 * links across scopes, or data-lightbox-group="none" to isolate one.
14 *
15 * Captions: data-caption on the link, else a sibling <figcaption>.
16 *
17 * Labels come from window.amaLightboxL10n (wp_localize_script) so they run
18 * through the theme's trans() layer.
19 *
20 * Public API: window.amaLightbox.open(linkEl) / .close()
21 */
22(function () {
23	'use strict';
24
25	var CONFIG = {
26		scopes: ['.editor-content', '.item__content-editor', '.is-item__media'],
27		imageExt: /\.(jpe?g|png|gif|webp|avif|svg)(\?.*)?$/i,
28		swipeThreshold: 50,     // px before a drag counts as a swipe
29		lockClass: 'ama-lightbox-lock'
30	};
31
32	var L10N = window.amaLightboxL10n || {};
33
34	function t(key, fallback) {
35		return L10N[key] || fallback;
36	}
37
38	var dialog = null;
39	var imgEl = null;
40	var captionEl = null;
41	var counterEl = null;
42	var prevBtn = null;
43	var nextBtn = null;
44	var frameEl = null;
45	var group = [];
46	var index = 0;
47	var lastFocus = null;
48	var supportsModal = typeof HTMLDialogElement === 'function' &&
49		typeof HTMLDialogElement.prototype.showModal === 'function';
50
51	function scopeSelector() {
52		return CONFIG.scopes.join(',');
53	}
54
55	/* An <a> that links straight to an image file and wraps nothing but one
56	   <img>
56. Anything else — a link with text, a link to a PDF, a link around
57	   a card — is left alone. */
58	function isImageLink(a) {
59		if (!a || a.tagName !== 'A') {
60			return false;
61		}
62
63		var href = a.getAttribute('href') || '';
64
65		if (!CONFIG.imageExt.test(href)) {
66			return false;
67		}
68
69		if (a.children.length !== 1) {
70			return false;
71		}
72
73		return a.firstElementChild && a.firstElementChild.tagName === 'IMG';
74	}
75
76	function inScope(a) {
77		return a.closest(scopeSelector());
78	}
79
80	function collect(a) {
81		var name = a.dataset.lightboxGroup;
82
83		if (name === 'none') {
84			return [a];
85		}
86
87		var pool;
88
89		if (name) {
90			pool = document.querySelectorAll('[data-lightbox-group="' + name + '"]');
91		} else {
92			var scope = inScope(a);
93			pool = scope ? scope.querySelectorAll('a[href]') : [a];
94		}
95
96		var links = Array.prototype.filter.call(pool, isImageLink);
97
98		return links.length ? links : [a];
99	}
100
101	function caption(a) {
102		if (a.dataset.caption) {
103			return a.dataset.caption;
104		}
105
106		var figure = a.closest('figure');
107		var cap = figure ? figure.querySelector('figcaption') : null;
108
109		return cap ? cap.textContent.trim() : '';
110	}
111
112	function button(className, label, path) {
113		var btn = document.createElement('button');
114
115		btn.type = 'button';
116		btn.className = className;
117		btn.setAttribute('aria-label', label);
118		btn.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
119			'<path d="' + path + '"/></svg>';
120
121		return btn;
122	}
123
124	function build() {
125		if (dialog) {
126			return;
127		}
128
129		dialog = document.createElement(supportsModal ? 'dialog' : 'div');
130		dialog.className = 'ama-lightbox';
131
132		if (!supportsModal) {
133			dialog.setAttribute('role', 'dialog');
134			dialog.setAttribute('aria-modal', 'true');
135			dialog.hidden = true;
136		}
137
138		frameEl = document.createElement('div');
139		frameEl.className = 'ama-lightbox__frame';
140
141		imgEl = document.createElement('img');
142		imgEl.className = 'ama-lightbox__img';
143		imgEl.alt = '';
144
145		captionEl = document.createElement('p');
146		captionEl.className = 'ama-lightbox__caption';
147
148		counterEl = document.createElement('p');
149		counterEl.className = 'ama-lightbox__counter';
150
151		var closeBtn = button('ama-lightbox__btn ama-lightbox__close',
152			t('close', 'Close'), 'M6 6l12 12M18 6L6 18');
153		prevBtn = button('ama-lightbox__btn ama-lightbox__prev',
154			t('prev', 'Previous'), 'M15 5l-7 7 7 7');
155		nextBtn = button('ama-lightbox__btn ama-lightbox__next',
156			t('next', 'Next'), 'M9 5l7 7-7 7');
157
158		frameEl.appendChild(imgEl);
159		frameEl.appendChild(captionEl);
160
161		dialog.appendChild(closeBtn);
162		dialog.appendChild(prevBtn);
163		dialog.appendChild(nextBtn);
164		dialog.appendChild(frameEl);
165		dialog.appendChild(counterEl);
166
167		document.body.appendChild(dialog);
168
169		closeBtn.addEventListener('click', close);
170		prevBtn.addEventListener('click', function () { step(-1); });
171		nextBtn.addEventListener('click', function () { step(1); });
172
173		/* Clicking the empty area around the image closes. */
174		dialog.addEventListener('click', function (e) {
175			if (e.target === dialog || e.target === frameEl) {
176				close();
177			}
178		});
179
180		dialog.addEventListener('cancel', function (e) {
181			e.preventDefault();
182			close();
183		});
184
185		bindSwipe();
186	}
187
188	function show(i) {
189		var total = group.length;
190
191		index = (i + total) % total;
192
193		var link = group[index];
194		var text = caption(link);
195		var thumb = link.querySelector('img');
196
197		dialog.classList.add('is-loading');
198
199		var loader = new Image();
200
201		loader.onload = function () {
202			if (group[index] !== link) {
203				return; // navigated on while this was loading
204			}
205
206			imgEl.src = loader.src;
207			imgEl.alt = thumb ? thumb.alt : '';
208			dialog.classList.remove('is-loading');
209			preloadNeighbours();
210		};
211		loader.onerror = function () {
212			dialog.classList.remove('is-loading');
213		};
214		loader.src = link.href;
215
216		captionEl.textContent = text;
217		captionEl.hidden = !text;
218
219		counterEl.textContent = total > 1 ? (index + 1) + ' / ' + total : '';
220		counterEl.hidden = total < 2;
221		prevBtn.hidden = total < 2;
222		nextBtn.hidden = total < 2;
223	}
224
225	function preloadNeighbours() {
226		if (group.length < 2) {
227			return;
228		}
229
230		[-1, 1].forEach(function (offset) {
231			var next = group[(index + offset + group.length) % group.length];
232			var pre = new Image();
233			pre.src = next.href;
234		});
235	}
236
237	function step(delta) {
238		if (group.length > 1) {
239			show(index + delta);
240		}
241	}
242
243	function open(link) {
244		build();
245
246		lastFocus = document.activeElement;
247		group = collect(link);
248		index = Math.max(0, group.indexOf(link));
249
250		document.documentElement.classList.add(CONFIG.lockClass);
251
252		if (supportsModal) {
253			dialog.showModal();
254		} else {
255			dialog.hidden = false;
256			dialog.classList.add('is-open');
257		}
258
259		show(index);
260		dialog.querySelector('.ama-lightbox__close').focus();
261	}
262
263	function close() {
264		if (!dialog) {
265			return;
266		}
267
268		document.documentElement.classList.remove(CONFIG.lockClass);
269
270		if (supportsModal) {
271			if (dialog.open) {
272				dialog.close();
273			}
274		} else {
275			dialog.hidden = true;
276			dialog.classList.remove('is-open');
277		}
278
279		imgEl.removeAttribute('src');
280		group = [];
281
282		if (lastFocus && typeof lastFocus.focus === 'function') {
283			lastFocus.focus();
284		}
285		lastFocus = null;
286	}
287
288	function isOpen() {
289		return dialog && (supportsModal ? dialog.open : !dialog.hidden);
290	}
291
292	/* Horizontal swipe changes image, downward swipe closes. */
293	function bindSwipe() {
294		var startX = 0;
295		var startY = 0;
296		var tracking = false;
297
298		frameEl.addEventListener('touchstart', function (e) {
299			if (e.touches.length !== 1) {
300				tracking = false;
301				return;
302			}
303			tracking = true;
304			startX = e.touches[0].clientX;
305			startY = e.touches[0].clientY;
306		}, { passive: true });
307
308		frameEl.addEventListener('touchend', function (e) {
309			if (!tracking) {
310				return;
311			}
312			tracking = false;
313
314			var touch = e.changedTouches[0];
315			var dx = touch.clientX - startX;
316			var dy = touch.clientY - startY;
317
318			if (Math.abs(dx) > Math.abs(dy)) {
319				if (Math.abs(dx) > CONFIG.swipeThreshold) {
320					step(dx < 0 ? 1 : -1);
321				}
322				return;
323			}
324
325			if (dy > CONFIG.swipeThreshold) {
326				close();
327			}
328		});
329	}
330
331	document.addEventListener('click', function (e) {
332		/* Let modified clicks open in a new tab as the visitor expects. */
333		if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
334			return;
335		}
336
337		var link = e.target.closest ? e.target.closest('a[href]') : null;
338
339		if (!link || !isImageLink(link) || !inScope(link)) {
340			return;
341		}
342
343		e.preventDefault();
344		open(link);
345	});
346
347	document.addEventListener('keydown', function (e) {
348		if (!isOpen()) {
349			return;
350		}
351
352		if (e.key === 'ArrowLeft') {
353			e.preventDefault();
354			step(-1);
355		} else if (e.key === 'ArrowRight') {
356			e.preventDefault();
357			step(1);
358		} else if (e.key === 'Escape' && !supportsModal) {
359			e.preventDefault();
360			close();
361		}
362	});
363
364	window.amaLightbox = { open: open, close: close, config: CONFIG };
365})();

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.