zoomora 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1142 @@
1
+ (function(factory) {
2
+ typeof define === "function" && define.amd ? define(factory) : factory();
3
+ })(function() {
4
+ "use strict";var __defProp = Object.defineProperty;
5
+ var __defProps = Object.defineProperties;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+
24
+ /*!
25
+ * Zoomora Lightbox Plugin v1.0.0
26
+ * A modern, responsive lightbox plugin with zoom, fullscreen, and gallery features
27
+ *
28
+ * Copyright (c) 2025 Faruk Ahmed (FrontTheme)
29
+ * Licensed under MIT License
30
+ */
31
+ (function(global, factory) {
32
+ if (typeof module === "object" && typeof module.exports === "object") {
33
+ module.exports = factory();
34
+ } else if (typeof define === "function" && define.amd) {
35
+ define(factory);
36
+ } else {
37
+ global.Zoomora = factory();
38
+ }
39
+ })(typeof window !== "undefined" ? window : void 0, function() {
40
+ class Zoomora {
41
+ /**
42
+ * Default configuration options
43
+ * @type {Object}
44
+ */
45
+ static get defaults() {
46
+ return {
47
+ selector: "[data-zoomora]",
48
+ showCounter: true,
49
+ showThumbnails: true,
50
+ showFullscreen: true,
51
+ showZoom: true,
52
+ transition: "fade",
53
+ // 'fade' or 'slide'
54
+ useHref: false,
55
+ // Use href instead of data-src
56
+ maxZoomScale: 3,
57
+ // Maximum zoom scale
58
+ zoomStep: 0.1,
59
+ // Zoom step for scroll wheel
60
+ animationDuration: 300,
61
+ // Animation duration in ms
62
+ onOpen: null,
63
+ // Callback when lightbox opens
64
+ onClose: null,
65
+ // Callback when lightbox closes
66
+ onNext: null,
67
+ // Callback when navigating to next item
68
+ onPrev: null,
69
+ // Callback when navigating to previous item
70
+ // Auto-hide controls
71
+ showAutoHideToggle: true,
72
+ // Show auto-hide toggle button
73
+ autoHideDelay: 3e3,
74
+ // Delay in ms before hiding controls
75
+ autoHideEnabled: false
76
+ // Default state of auto-hide
77
+ };
78
+ }
79
+ /**
80
+ * Constructor
81
+ * @param {Object} options - Configuration options
82
+ */
83
+ constructor(options = {}) {
84
+ this.options = __spreadValues(__spreadValues({}, Zoomora.defaults), options);
85
+ this.currentIndex = 0;
86
+ this.items = [];
87
+ this.isZoomed = false;
88
+ this.isFullscreen = false;
89
+ this.isDragging = false;
90
+ this.thumbnailsVisible = true;
91
+ this.startX = 0;
92
+ this.startY = 0;
93
+ this.currentX = 0;
94
+ this.currentY = 0;
95
+ this.currentScale = 1;
96
+ this.isInitialized = false;
97
+ this.eventListeners = /* @__PURE__ */ new Map();
98
+ this.autoHideEnabled = false;
99
+ this.autoHideTimeout = null;
100
+ this.autoHideDelay = 3e3;
101
+ this.isControlsVisible = true;
102
+ this.idleTimer = null;
103
+ this.boundMethods = {
104
+ handleDocumentClick: this.handleDocumentClick.bind(this),
105
+ handleKeydown: this.handleKeydown.bind(this),
106
+ handleFullscreenChange: this.handleFullscreenChange.bind(this),
107
+ handleZoomoraClick: this.handleZoomoraClick.bind(this),
108
+ handleContentClick: this.handleContentClick.bind(this),
109
+ handleMouseDown: this.handleMouseDown.bind(this),
110
+ handleMouseMove: this.handleMouseMove.bind(this),
111
+ handleMouseUp: this.handleMouseUp.bind(this),
112
+ handleTouchStart: this.handleTouchStart.bind(this),
113
+ handleTouchMove: this.handleTouchMove.bind(this),
114
+ handleTouchEnd: this.handleTouchEnd.bind(this)
115
+ };
116
+ this.init();
117
+ }
118
+ /**
119
+ * Initialize the plugin
120
+ */
121
+ init() {
122
+ if (this.isInitialized) {
123
+ console.warn("Zoomora: Plugin already initialized");
124
+ return;
125
+ }
126
+ try {
127
+ this.createLightbox();
128
+ this.bindEvents();
129
+ this.isInitialized = true;
130
+ } catch (error) {
131
+ console.error("Zoomora: Initialization failed", error);
132
+ }
133
+ }
134
+ /**
135
+ * Static method to bind to anchor tags with href
136
+ * @param {string} selector - CSS selector
137
+ * @param {Object} options - Configuration options
138
+ * @returns {Zoomora} - New Zoomora instance
139
+ */
140
+ static bind(selector, options = {}) {
141
+ const enhancedOptions = __spreadProps(__spreadValues({}, options), {
142
+ selector,
143
+ useHref: true
144
+ });
145
+ return new Zoomora(enhancedOptions);
146
+ }
147
+ /**
148
+ * Create lightbox HTML structure
149
+ */
150
+ createLightbox() {
151
+ if (document.getElementById("zoomoraLightbox")) {
152
+ console.warn("Zoomora: Lightbox already exists in DOM");
153
+ return;
154
+ }
155
+ const zoomoraHTML = `
156
+ <div class="zoomora" id="zoomoraLightbox">
157
+ <div class="zoomora-container">
158
+ <div class="zoomora-header" id="zoomoraHeader">
159
+ <div class="zoomora-counter" id="zoomoraCounter">1 / 1</div>
160
+ <div class="zoomora-controls">
161
+ <button class="zoomora-btn" id="zoomoraThumbnailBtn" title="Toggle Thumbnails">
162
+ <i class="zoomorai-thumbnails"></i>
163
+ </button>
164
+ <button class="zoomora-btn" id="zoomoraAutoHideBtn" title="Toggle Auto-Hide Controls">
165
+ <i class="zoomorai-controls-off"></i>
166
+ </button>
167
+ <button class="zoomora-btn" id="zoomoraZoomBtn" title="Zoom">
168
+ <i class="zoomorai-zoom"></i>
169
+ </button>
170
+ <button class="zoomora-btn" id="zoomoraFullscreenBtn" title="Fullscreen">
171
+ <i class="zoomorai-expand"></i>
172
+ </button>
173
+ <button class="zoomora-btn" id="zoomoraCollapseBtn" title="Exit Fullscreen" style="display: none;">
174
+ <i class="zoomorai-collapse"></i>
175
+ </button>
176
+ <button class="zoomora-btn" id="zoomoraCloseBtn" title="Close">
177
+ <i class="zoomorai-close"></i>
178
+ </button>
179
+ </div>
180
+ </div>
181
+
182
+ <div class="zoomora-content" id="zoomoraContent">
183
+ <div class="zoomora-loading" id="zoomoraLoading"></div>
184
+ <div class="zoomora-slides-container" id="zoomoraSlidesContainer"></div>
185
+ </div>
186
+
187
+ <button class="zoomora-nav prev" id="zoomoraPrevBtn">
188
+ <i class="zoomorai-arrow-left"></i>
189
+ </button>
190
+ <button class="zoomora-nav next" id="zoomoraNextBtn">
191
+ <i class="zoomorai-arrow-right"></i>
192
+ </button>
193
+
194
+ <div class="zoomora-caption" id="zoomoraCaption"></div>
195
+ <div class="zoomora-thumbnails" id="zoomoraThumbs"></div>
196
+ </div>
197
+ </div>
198
+ `;
199
+ document.body.insertAdjacentHTML("beforeend", zoomoraHTML);
200
+ this.zoomora = document.getElementById("zoomoraLightbox");
201
+ this.header = document.getElementById("zoomoraHeader");
202
+ this.content = document.getElementById("zoomoraContent");
203
+ this.slidesContainer = document.getElementById("zoomoraSlidesContainer");
204
+ this.counter = document.getElementById("zoomoraCounter");
205
+ this.thumbs = document.getElementById("zoomoraThumbs");
206
+ this.loading = document.getElementById("zoomoraLoading");
207
+ this.caption = document.getElementById("zoomoraCaption");
208
+ this.prevBtn = document.getElementById("zoomoraPrevBtn");
209
+ this.nextBtn = document.getElementById("zoomoraNextBtn");
210
+ if (!this.zoomora || !this.content || !this.counter || !this.thumbs || !this.loading || !this.caption) {
211
+ throw new Error("Failed to create required DOM elements");
212
+ }
213
+ }
214
+ /**
215
+ * Bind event listeners
216
+ */
217
+ bindEvents() {
218
+ this.addEventListener(document, "click", this.boundMethods.handleDocumentClick);
219
+ this.addEventListener(document.getElementById("zoomoraCloseBtn"), "click", () => this.close());
220
+ this.addEventListener(document.getElementById("zoomoraPrevBtn"), "click", () => this.prev());
221
+ this.addEventListener(document.getElementById("zoomoraNextBtn"), "click", () => this.next());
222
+ this.addEventListener(document.getElementById("zoomoraZoomBtn"), "click", () => this.toggleZoom());
223
+ this.addEventListener(document.getElementById("zoomoraFullscreenBtn"), "click", () => this.toggleFullscreen());
224
+ this.addEventListener(document.getElementById("zoomoraCollapseBtn"), "click", () => this.exitFullscreen());
225
+ this.addEventListener(document.getElementById("zoomoraThumbnailBtn"), "click", () => this.toggleThumbnails());
226
+ this.addEventListener(document, "fullscreenchange", this.boundMethods.handleFullscreenChange);
227
+ this.addEventListener(document, "webkitfullscreenchange", this.boundMethods.handleFullscreenChange);
228
+ this.addEventListener(document, "mozfullscreenchange", this.boundMethods.handleFullscreenChange);
229
+ this.addEventListener(document, "MSFullscreenChange", this.boundMethods.handleFullscreenChange);
230
+ this.addEventListener(document, "keydown", this.boundMethods.handleKeydown);
231
+ this.addEventListener(this.zoomora, "click", this.boundMethods.handleZoomoraClick);
232
+ this.addEventListener(this.content, "click", this.boundMethods.handleContentClick);
233
+ this.addEventListener(this.content, "mousedown", this.boundMethods.handleMouseDown);
234
+ this.addEventListener(this.content, "mousemove", this.boundMethods.handleMouseMove);
235
+ this.addEventListener(this.content, "mouseup", this.boundMethods.handleMouseUp);
236
+ this.addEventListener(this.content, "mouseleave", this.boundMethods.handleMouseUp);
237
+ this.addEventListener(this.content, "touchstart", this.boundMethods.handleTouchStart, { passive: false });
238
+ this.addEventListener(this.content, "touchmove", this.boundMethods.handleTouchMove, { passive: false });
239
+ this.addEventListener(this.content, "touchend", this.boundMethods.handleTouchEnd);
240
+ this.addEventListener(document.getElementById("zoomoraAutoHideBtn"), "click", () => this.toggleAutoHide());
241
+ this.addEventListener(this.zoomora, "mousemove", () => this.handleUserActivity());
242
+ this.addEventListener(this.zoomora, "touchstart", () => this.handleUserActivity());
243
+ this.addEventListener(this.zoomora, "keydown", () => this.handleUserActivity());
244
+ }
245
+ /**
246
+ * Add event listener with cleanup tracking
247
+ * @param {Element} element - DOM element
248
+ * @param {string} event - Event type
249
+ * @param {Function} handler - Event handler
250
+ * @param {Object} options - Event options
251
+ */
252
+ addEventListener(element, event, handler, options = {}) {
253
+ if (!element) return;
254
+ element.addEventListener(event, handler, options);
255
+ const key = `${element.constructor.name}_${event}`;
256
+ if (!this.eventListeners.has(key)) {
257
+ this.eventListeners.set(key, []);
258
+ }
259
+ this.eventListeners.get(key).push({ element, event, handler, options });
260
+ }
261
+ /**
262
+ * Handle document click events
263
+ * @param {Event} e - Click event
264
+ */
265
+ handleDocumentClick(e) {
266
+ const target = e.target.closest(this.options.selector);
267
+ if (target) {
268
+ e.preventDefault();
269
+ this.open(target);
270
+ }
271
+ }
272
+ /**
273
+ * Handle keyboard events
274
+ * @param {KeyboardEvent} e - Keyboard event
275
+ */
276
+ handleKeydown(e) {
277
+ if (!this.zoomora.classList.contains("active")) return;
278
+ switch (e.key) {
279
+ case "Escape":
280
+ if (this.isFullscreen) {
281
+ this.exitFullscreen();
282
+ } else {
283
+ this.close();
284
+ }
285
+ break;
286
+ case "ArrowLeft":
287
+ this.prev();
288
+ break;
289
+ case "ArrowRight":
290
+ this.next();
291
+ break;
292
+ case "z":
293
+ case "Z":
294
+ this.toggleZoom();
295
+ break;
296
+ case "f":
297
+ case "F":
298
+ this.toggleFullscreen();
299
+ break;
300
+ case "t":
301
+ case "T":
302
+ this.toggleThumbnails();
303
+ break;
304
+ }
305
+ }
306
+ /**
307
+ * Handle fullscreen change events
308
+ */
309
+ handleFullscreenChange() {
310
+ const isInFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
311
+ if (!isInFullscreen && this.isFullscreen) {
312
+ this.isFullscreen = false;
313
+ this.zoomora.classList.remove("fullscreen");
314
+ this.updateButtons();
315
+ }
316
+ }
317
+ /**
318
+ * Handle zoomora container clicks
319
+ * @param {Event} e - Click event
320
+ */
321
+ handleZoomoraClick(e) {
322
+ if (e.target === this.zoomora) {
323
+ this.close();
324
+ }
325
+ }
326
+ /**
327
+ * Handle content clicks
328
+ * @param {Event} e - Click event
329
+ */
330
+ handleContentClick(e) {
331
+ const media = e.target.closest(".zoomora-media");
332
+ if (media && !media.classList.contains("no-zoom") && !this.isDragging) {
333
+ this.toggleZoom();
334
+ }
335
+ }
336
+ /**
337
+ * Handle mouse down events
338
+ * @param {MouseEvent} e - Mouse event
339
+ */
340
+ handleMouseDown(e) {
341
+ this.startDrag(e);
342
+ }
343
+ /**
344
+ * Handle mouse move events
345
+ * @param {MouseEvent} e - Mouse event
346
+ */
347
+ handleMouseMove(e) {
348
+ this.drag(e);
349
+ }
350
+ /**
351
+ * Handle mouse up events
352
+ */
353
+ handleMouseUp() {
354
+ this.endDrag();
355
+ }
356
+ /**
357
+ * Handle touch start events
358
+ * @param {TouchEvent} e - Touch event
359
+ */
360
+ handleTouchStart(e) {
361
+ this.startDrag(e);
362
+ }
363
+ /**
364
+ * Handle touch move events
365
+ * @param {TouchEvent} e - Touch event
366
+ */
367
+ handleTouchMove(e) {
368
+ this.drag(e);
369
+ }
370
+ /**
371
+ * Handle touch end events
372
+ */
373
+ handleTouchEnd() {
374
+ this.endDrag();
375
+ }
376
+ /**
377
+ * Get image source from element
378
+ * @param {Element} item - DOM element
379
+ * @returns {string} - Image source URL
380
+ */
381
+ getImageSource(item) {
382
+ const src = item.dataset.src || item.src || item.getAttribute("src");
383
+ const type = item.dataset.type || "image";
384
+ if (type === "video" && src) {
385
+ return this.convertToEmbedUrl(src);
386
+ }
387
+ return src;
388
+ }
389
+ /**
390
+ * Convert YouTube URLs to embed format
391
+ * @param url
392
+ * @returns {*|string}
393
+ */
394
+ convertToEmbedUrl(url) {
395
+ if (url.includes("youtube.com/embed/")) {
396
+ return url;
397
+ }
398
+ let videoId = null;
399
+ let startTime = null;
400
+ if (url.includes("youtube.com/watch")) {
401
+ const urlParams = new URLSearchParams(url.split("?")[1]);
402
+ videoId = urlParams.get("v");
403
+ const t = urlParams.get("t");
404
+ if (t) {
405
+ startTime = t.replace("s", "");
406
+ }
407
+ } else if (url.includes("youtu.be/")) {
408
+ const parts = url.split("/");
409
+ const videoAndParams = parts[parts.length - 1];
410
+ if (videoAndParams.includes("?")) {
411
+ videoId = videoAndParams.split("?")[0];
412
+ const urlParams = new URLSearchParams(videoAndParams.split("?")[1]);
413
+ const t = urlParams.get("t");
414
+ if (t) startTime = t.replace("s", "");
415
+ } else {
416
+ videoId = videoAndParams;
417
+ }
418
+ }
419
+ if (videoId) {
420
+ let embedUrl = `https://www.youtube.com/embed/${videoId}`;
421
+ const params = new URLSearchParams();
422
+ if (startTime) {
423
+ params.append("start", startTime);
424
+ }
425
+ params.append("rel", "0");
426
+ params.append("modestbranding", "1");
427
+ if (params.toString()) {
428
+ embedUrl += "?" + params.toString();
429
+ }
430
+ return embedUrl;
431
+ }
432
+ return url;
433
+ }
434
+ /**
435
+ * Get thumbnail source from element
436
+ * @param {Element} item - DOM element
437
+ * @returns {string} - Thumbnail source URL
438
+ */
439
+ getThumbnailSource(item) {
440
+ const type = item.dataset.type || "image";
441
+ if (type === "video") {
442
+ return item.src || item.getAttribute("src");
443
+ }
444
+ return item.dataset.src || item.src || item.getAttribute("src");
445
+ }
446
+ /**
447
+ * Get group identifier from element
448
+ * @param {Element} element - DOM element
449
+ * @returns {string} - Group identifier
450
+ */
451
+ getGroup(element) {
452
+ if (this.options.useHref) {
453
+ return element.dataset.lightbox || element.dataset.gallery || "default";
454
+ }
455
+ return element.dataset.zoomora || "default";
456
+ }
457
+ /**
458
+ * Open lightbox with specified element
459
+ * @param {Element} element - DOM element to open
460
+ */
461
+ open(element) {
462
+ if (!element) return;
463
+ const group = this.getGroup(element);
464
+ let groupSelector;
465
+ if (this.options.useHref) {
466
+ if (element.dataset.lightbox) {
467
+ groupSelector = `[data-lightbox="${group}"]`;
468
+ } else if (element.dataset.gallery) {
469
+ groupSelector = `[data-gallery="${group}"]`;
470
+ } else {
471
+ groupSelector = this.options.selector;
472
+ }
473
+ } else {
474
+ groupSelector = `[data-zoomora="${group}"]`;
475
+ }
476
+ this.items = Array.from(document.querySelectorAll(groupSelector));
477
+ this.currentIndex = Math.max(0, this.items.indexOf(element));
478
+ this.isZoomed = false;
479
+ this.isFullscreen = false;
480
+ this.thumbnailsVisible = true;
481
+ this.currentX = 0;
482
+ this.currentY = 0;
483
+ this.currentScale = 1;
484
+ this.zoomora.classList.add("active");
485
+ document.body.style.overflow = "hidden";
486
+ this.loadContent();
487
+ this.updateThumbnails();
488
+ this.updateCounter();
489
+ this.updateNavigation();
490
+ this.updateCaption();
491
+ this.updateButtons();
492
+ if (this.autoHideEnabled) {
493
+ this.startAutoHideTimer();
494
+ } else {
495
+ this.showControls();
496
+ }
497
+ if (typeof this.options.onOpen === "function") {
498
+ this.options.onOpen(element, this.currentIndex);
499
+ }
500
+ }
501
+ /**
502
+ * Close lightbox
503
+ */
504
+ close() {
505
+ if (this.isFullscreen) {
506
+ this.exitFullscreen();
507
+ }
508
+ this.zoomora.classList.remove("active");
509
+ document.body.style.overflow = "";
510
+ this.isZoomed = false;
511
+ this.isFullscreen = false;
512
+ this.currentX = 0;
513
+ this.currentY = 0;
514
+ this.currentScale = 1;
515
+ const media = this.content.querySelector(".zoomora-media");
516
+ if (media) {
517
+ media.classList.remove("zoomed");
518
+ media.style.transform = "";
519
+ media.style.cursor = "";
520
+ }
521
+ this.zoomora.classList.remove("fullscreen");
522
+ this.updateButtons();
523
+ this.stopAutoHideTimer();
524
+ this.showControls();
525
+ if (typeof this.options.onClose === "function") {
526
+ this.options.onClose(this.currentIndex);
527
+ }
528
+ }
529
+ /**
530
+ * Navigate to previous item
531
+ */
532
+ prev() {
533
+ if (this.currentIndex > 0) {
534
+ this.currentIndex--;
535
+ this.loadContent();
536
+ this.updateCounter();
537
+ this.updateThumbnails();
538
+ this.updateNavigation();
539
+ this.updateCaption();
540
+ if (typeof this.options.onPrev === "function") {
541
+ this.options.onPrev(this.currentIndex);
542
+ }
543
+ }
544
+ }
545
+ /**
546
+ * Navigate to next item
547
+ */
548
+ next() {
549
+ if (this.currentIndex < this.items.length - 1) {
550
+ this.currentIndex++;
551
+ this.loadContent();
552
+ this.updateCounter();
553
+ this.updateThumbnails();
554
+ this.updateNavigation();
555
+ this.updateCaption();
556
+ if (typeof this.options.onNext === "function") {
557
+ this.options.onNext(this.currentIndex);
558
+ }
559
+ }
560
+ }
561
+ /**
562
+ * Load content for current item
563
+ */
564
+ loadContent() {
565
+ if (!this.items[this.currentIndex]) return;
566
+ const item = this.items[this.currentIndex];
567
+ const src = this.getImageSource(item);
568
+ const type = item.dataset.type || "image";
569
+ if (!src) {
570
+ console.error("Zoomora: No source found for item");
571
+ return;
572
+ }
573
+ this.loading.style.display = "block";
574
+ if (this.slidesContainer) {
575
+ this.slidesContainer.innerHTML = "";
576
+ }
577
+ setTimeout(() => {
578
+ this.renderContent(src, type, item);
579
+ }, 100);
580
+ }
581
+ /**
582
+ * Render content based on type
583
+ * @param {string} src - Source URL
584
+ * @param {string} type - Content type
585
+ * @param {Element} item - Original item element
586
+ */
587
+ renderContent(src, type, item) {
588
+ const slideContainer = document.createElement("div");
589
+ slideContainer.className = "zoomora-slide-container";
590
+ const slide = document.createElement("div");
591
+ slide.className = `zoomora-slide ${this.options.transition === "slide" ? "slide-effect" : "fade-effect"}`;
592
+ slide.style.opacity = "0";
593
+ slide.style.transform = this.options.transition === "slide" ? "translateX(100%)" : "scale(0.8)";
594
+ let mediaElement;
595
+ try {
596
+ switch (type) {
597
+ case "video":
598
+ if (src.endsWith(".mp4") || src.endsWith(".webm") || src.endsWith(".ogg")) {
599
+ const videoContainer = document.createElement("div");
600
+ videoContainer.className = "zoomora-video-container";
601
+ mediaElement = document.createElement("video");
602
+ mediaElement.src = src;
603
+ mediaElement.className = "zoomora-video";
604
+ mediaElement.setAttribute("preload", "metadata");
605
+ const posterSrc = item.src || item.getAttribute("src");
606
+ if (posterSrc) {
607
+ mediaElement.setAttribute("poster", posterSrc);
608
+ }
609
+ const playButton = document.createElement("div");
610
+ playButton.className = "zoomora-play-button";
611
+ playButton.innerHTML = `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>`;
612
+ playButton.addEventListener("click", () => {
613
+ mediaElement.play();
614
+ playButton.style.display = "none";
615
+ mediaElement.setAttribute("controls", "");
616
+ });
617
+ mediaElement.addEventListener("ended", () => {
618
+ playButton.style.display = "flex";
619
+ });
620
+ videoContainer.appendChild(mediaElement);
621
+ videoContainer.appendChild(playButton);
622
+ slideContainer.appendChild(videoContainer);
623
+ } else {
624
+ mediaElement = document.createElement("iframe");
625
+ mediaElement.src = src;
626
+ mediaElement.className = "zoomora-video";
627
+ mediaElement.setAttribute("allowfullscreen", "");
628
+ mediaElement.setAttribute("frameborder", "0");
629
+ slideContainer.appendChild(mediaElement);
630
+ }
631
+ break;
632
+ case "iframe":
633
+ mediaElement = document.createElement("iframe");
634
+ mediaElement.src = src;
635
+ mediaElement.className = "zoomora-iframe";
636
+ mediaElement.setAttribute("frameborder", "0");
637
+ slideContainer.appendChild(mediaElement);
638
+ break;
639
+ default:
640
+ mediaElement = document.createElement("img");
641
+ mediaElement.src = src;
642
+ mediaElement.className = "zoomora-media";
643
+ mediaElement.alt = item.alt || item.title || "";
644
+ mediaElement.onload = () => {
645
+ this.loading.style.display = "none";
646
+ this.checkZoomability(mediaElement);
647
+ this.animateSlideIn(slide);
648
+ };
649
+ mediaElement.onerror = () => {
650
+ this.loading.style.display = "none";
651
+ console.error("Zoomora: Failed to load image:", src);
652
+ this.animateSlideIn(slide);
653
+ };
654
+ slideContainer.appendChild(mediaElement);
655
+ }
656
+ slide.appendChild(slideContainer);
657
+ this.slidesContainer.innerHTML = "";
658
+ this.slidesContainer.appendChild(slide);
659
+ if (type !== "image") {
660
+ this.loading.style.display = "none";
661
+ this.animateSlideIn(slide);
662
+ }
663
+ } catch (error) {
664
+ console.error("Zoomora: Error rendering content:", error);
665
+ this.loading.style.display = "none";
666
+ }
667
+ }
668
+ // Animation helper
669
+ animateSlideIn(slide) {
670
+ slide.offsetHeight;
671
+ slide.classList.add("active");
672
+ if (this.options.transition === "slide") {
673
+ slide.style.transition = `transform ${this.options.animationDuration}ms ease, opacity ${this.options.animationDuration}ms ease`;
674
+ slide.style.transform = "translateX(0)";
675
+ slide.style.opacity = "1";
676
+ } else {
677
+ slide.style.transition = `transform ${this.options.animationDuration}ms ease, opacity ${this.options.animationDuration}ms ease`;
678
+ slide.style.transform = "scale(1)";
679
+ slide.style.opacity = "1";
680
+ }
681
+ }
682
+ // Toggle auto-hide controls
683
+ toggleAutoHide() {
684
+ this.autoHideEnabled = !this.autoHideEnabled;
685
+ const autoHideBtn = document.getElementById("zoomoraAutoHideBtn");
686
+ if (this.autoHideEnabled) {
687
+ autoHideBtn.classList.add("active");
688
+ autoHideBtn.title = "Disable Auto-Hide Controls";
689
+ this.startAutoHideTimer();
690
+ } else {
691
+ autoHideBtn.classList.remove("active");
692
+ autoHideBtn.title = "Enable Auto-Hide Controls";
693
+ this.stopAutoHideTimer();
694
+ this.showControls();
695
+ }
696
+ }
697
+ // Handle user activity
698
+ handleUserActivity() {
699
+ if (!this.autoHideEnabled) return;
700
+ this.showControls();
701
+ this.startAutoHideTimer();
702
+ }
703
+ // Start auto-hide timer
704
+ startAutoHideTimer() {
705
+ this.stopAutoHideTimer();
706
+ this.autoHideTimeout = setTimeout(() => {
707
+ this.hideControls();
708
+ }, this.options.autoHideDelay);
709
+ }
710
+ // Stop auto-hide timer
711
+ stopAutoHideTimer() {
712
+ if (this.autoHideTimeout) {
713
+ clearTimeout(this.autoHideTimeout);
714
+ this.autoHideTimeout = null;
715
+ }
716
+ }
717
+ // Show controls
718
+ showControls() {
719
+ if (this.isControlsVisible) return;
720
+ this.isControlsVisible = true;
721
+ this.zoomora.classList.remove("controls-hidden");
722
+ if (this.header) {
723
+ this.header.style.transform = "translateY(0)";
724
+ this.header.style.opacity = "1";
725
+ }
726
+ if (this.caption) {
727
+ this.caption.style.transform = "translateX(-50%) translateY(0)";
728
+ this.caption.style.opacity = "1";
729
+ }
730
+ if (this.thumbs) {
731
+ this.thumbs.style.transform = "translateX(-50%) translateY(0)";
732
+ this.thumbs.style.opacity = "1";
733
+ }
734
+ if (this.prevBtn) {
735
+ this.prevBtn.style.transform = "translateX(0)";
736
+ this.prevBtn.style.opacity = "1";
737
+ }
738
+ if (this.nextBtn) {
739
+ this.nextBtn.style.transform = "translateX(0)";
740
+ this.nextBtn.style.opacity = "1";
741
+ }
742
+ }
743
+ // Hide controls
744
+ hideControls() {
745
+ if (!this.isControlsVisible) return;
746
+ this.isControlsVisible = false;
747
+ this.zoomora.classList.add("controls-hidden");
748
+ if (this.header) {
749
+ this.header.style.transform = "translateY(-100%)";
750
+ this.header.style.opacity = "0";
751
+ }
752
+ if (this.caption) {
753
+ this.caption.style.transform = "translateX(-50%) translateY(100%)";
754
+ this.caption.style.opacity = "0";
755
+ }
756
+ if (this.thumbs) {
757
+ this.thumbs.style.transform = "translateX(-50%) translateY(100%)";
758
+ this.thumbs.style.opacity = "0";
759
+ }
760
+ if (this.prevBtn) {
761
+ this.prevBtn.style.transform = "translateX(-100%)";
762
+ this.prevBtn.style.opacity = "0";
763
+ }
764
+ if (this.nextBtn) {
765
+ this.nextBtn.style.transform = "translateX(100%)";
766
+ this.nextBtn.style.opacity = "0";
767
+ }
768
+ }
769
+ /**
770
+ * Check if image can be zoomed
771
+ * @param {HTMLImageElement} img - Image element
772
+ */
773
+ checkZoomability(img) {
774
+ if (!img || !img.naturalWidth || !img.naturalHeight) return;
775
+ const imgRect = img.getBoundingClientRect();
776
+ const renderedWidth = imgRect.width;
777
+ const renderedHeight = imgRect.height;
778
+ const naturalWidth = img.naturalWidth;
779
+ const naturalHeight = img.naturalHeight;
780
+ const isScaledDown = naturalWidth > renderedWidth || naturalHeight > renderedHeight;
781
+ const zoomRatioX = naturalWidth / renderedWidth;
782
+ const zoomRatioY = naturalHeight / renderedHeight;
783
+ const maxZoomRatio = Math.max(zoomRatioX, zoomRatioY);
784
+ img.dataset.zoomRatio = maxZoomRatio;
785
+ img.dataset.naturalWidth = naturalWidth;
786
+ img.dataset.naturalHeight = naturalHeight;
787
+ img.dataset.renderedWidth = renderedWidth;
788
+ img.dataset.renderedHeight = renderedHeight;
789
+ if (isScaledDown && maxZoomRatio > 1.1) {
790
+ img.classList.remove("no-zoom");
791
+ } else {
792
+ img.classList.add("no-zoom");
793
+ }
794
+ }
795
+ /**
796
+ * Update counter display
797
+ */
798
+ updateCounter() {
799
+ if (this.options.showCounter && this.counter) {
800
+ this.counter.textContent = `${this.currentIndex + 1} / ${this.items.length}`;
801
+ }
802
+ }
803
+ /**
804
+ * Update caption display
805
+ */
806
+ updateCaption() {
807
+ if (!this.caption || !this.items[this.currentIndex]) return;
808
+ const item = this.items[this.currentIndex];
809
+ const caption = item.dataset.caption || item.title || "";
810
+ if (caption) {
811
+ this.caption.textContent = caption;
812
+ this.caption.style.transform = "translateX(-50%) translateY(0)";
813
+ this.caption.style.opacity = "1";
814
+ this.caption.style.pointerEvents = "auto";
815
+ this.caption.style.transition = "opacity 0.3s ease, transform 0.3s ease";
816
+ } else {
817
+ this.caption.style.transform = "translateX(-50%) translateY(100%)";
818
+ this.caption.style.opacity = "0";
819
+ this.caption.style.pointerEvents = "none";
820
+ this.caption.style.transition = "opacity 0.3s ease, transform 0.3s ease";
821
+ }
822
+ }
823
+ /**
824
+ * Update thumbnails display
825
+ */
826
+ updateThumbnails() {
827
+ if (!this.options.showThumbnails || !this.thumbs) return;
828
+ if (this.items.length === 1) {
829
+ this.caption.classList.add("single-item-thumbnail");
830
+ this.thumbs.style.display = "none";
831
+ return;
832
+ }
833
+ this.caption.classList.remove("single-item-thumbnail");
834
+ this.thumbs.style.display = "flex";
835
+ this.thumbs.innerHTML = "";
836
+ this.items.forEach((item, index) => {
837
+ const thumb = document.createElement("div");
838
+ thumb.className = "zoomora-thumb";
839
+ if (index === this.currentIndex) thumb.classList.add("active");
840
+ const img = document.createElement("img");
841
+ const thumbSrc = this.getThumbnailSource(item);
842
+ if (thumbSrc) {
843
+ img.src = thumbSrc;
844
+ img.alt = item.alt || item.title || "";
845
+ img.onerror = () => {
846
+ thumb.style.display = "none";
847
+ };
848
+ }
849
+ thumb.appendChild(img);
850
+ thumb.addEventListener("click", () => {
851
+ this.currentIndex = index;
852
+ this.loadContent();
853
+ this.updateCounter();
854
+ this.updateThumbnails();
855
+ this.updateNavigation();
856
+ this.updateCaption();
857
+ });
858
+ this.thumbs.appendChild(thumb);
859
+ });
860
+ }
861
+ /**
862
+ * Update navigation buttons
863
+ */
864
+ updateNavigation() {
865
+ const prevBtn = document.getElementById("zoomoraPrevBtn");
866
+ const nextBtn = document.getElementById("zoomoraNextBtn");
867
+ if (prevBtn) {
868
+ prevBtn.style.display = this.currentIndex > 0 ? "flex" : "none";
869
+ }
870
+ if (nextBtn) {
871
+ nextBtn.style.display = this.currentIndex < this.items.length - 1 ? "flex" : "none";
872
+ }
873
+ }
874
+ /**
875
+ * Update control buttons
876
+ */
877
+ updateButtons() {
878
+ const fullscreenBtn = document.getElementById("zoomoraFullscreenBtn");
879
+ const collapseBtn = document.getElementById("zoomoraCollapseBtn");
880
+ if (fullscreenBtn && collapseBtn) {
881
+ if (this.isFullscreen) {
882
+ fullscreenBtn.style.display = "none";
883
+ collapseBtn.style.display = "flex";
884
+ } else {
885
+ fullscreenBtn.style.display = "flex";
886
+ collapseBtn.style.display = "none";
887
+ }
888
+ }
889
+ }
890
+ /**
891
+ * Toggle zoom state
892
+ */
893
+ toggleZoom() {
894
+ const media = this.content.querySelector(".zoomora-media");
895
+ if (!media || media.tagName !== "IMG" || media.classList.contains("no-zoom")) return;
896
+ this.isZoomed = !this.isZoomed;
897
+ media.classList.toggle("zoomed", this.isZoomed);
898
+ if (this.isZoomed) {
899
+ const zoomRatio = parseFloat(media.dataset.zoomRatio) || 2;
900
+ let zoomScale = Math.min(zoomRatio, this.options.maxZoomScale);
901
+ if (zoomRatio > 2.5) {
902
+ zoomScale = Math.min(zoomRatio * 0.7, 2.5);
903
+ }
904
+ media.style.transform = `scale(${zoomScale})`;
905
+ media.style.cursor = "grab";
906
+ media.dataset.currentZoomScale = zoomScale;
907
+ this.currentScale = zoomScale;
908
+ } else {
909
+ media.style.transform = "";
910
+ media.style.cursor = "";
911
+ this.currentX = 0;
912
+ this.currentY = 0;
913
+ this.currentScale = 1;
914
+ }
915
+ }
916
+ /**
917
+ * Toggle fullscreen mode
918
+ */
919
+ toggleFullscreen() {
920
+ if (!this.isFullscreen) {
921
+ this.enterFullscreen();
922
+ } else {
923
+ this.exitFullscreen();
924
+ }
925
+ }
926
+ /**
927
+ * Enter fullscreen mode
928
+ */
929
+ enterFullscreen() {
930
+ const element = this.zoomora;
931
+ const requestFullscreen = element.requestFullscreen || element.webkitRequestFullscreen || element.mozRequestFullScreen || element.msRequestFullscreen;
932
+ if (requestFullscreen) {
933
+ requestFullscreen.call(element).then(() => {
934
+ this.isFullscreen = true;
935
+ this.zoomora.classList.add("fullscreen");
936
+ this.updateButtons();
937
+ }).catch((err) => {
938
+ console.warn("Zoomora: Could not enter fullscreen:", err);
939
+ this.fallbackFullscreen();
940
+ });
941
+ } else {
942
+ this.fallbackFullscreen();
943
+ }
944
+ }
945
+ /**
946
+ * Exit fullscreen mode
947
+ */
948
+ exitFullscreen() {
949
+ const isInFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
950
+ if (isInFullscreen) {
951
+ const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen;
952
+ if (exitFullscreen) {
953
+ exitFullscreen.call(document).then(() => {
954
+ this.isFullscreen = false;
955
+ this.zoomora.classList.remove("fullscreen");
956
+ this.updateButtons();
957
+ }).catch((err) => {
958
+ console.warn("Zoomora: Could not exit fullscreen:", err);
959
+ this.fallbackExitFullscreen();
960
+ });
961
+ } else {
962
+ this.fallbackExitFullscreen();
963
+ }
964
+ } else {
965
+ this.fallbackExitFullscreen();
966
+ }
967
+ }
968
+ /**
969
+ * Fallback fullscreen using CSS
970
+ */
971
+ fallbackFullscreen() {
972
+ this.isFullscreen = true;
973
+ this.zoomora.classList.add("fullscreen");
974
+ this.updateButtons();
975
+ }
976
+ /**
977
+ * Exit CSS fullscreen
978
+ */
979
+ fallbackExitFullscreen() {
980
+ this.isFullscreen = false;
981
+ this.zoomora.classList.remove("fullscreen");
982
+ this.updateButtons();
983
+ }
984
+ /**
985
+ * Toggle thumbnails visibility
986
+ */
987
+ toggleThumbnails() {
988
+ if (this.items.length === 1) return;
989
+ this.thumbnailsVisible = !this.thumbnailsVisible;
990
+ if (this.thumbs) {
991
+ this.thumbs.classList.toggle("thumbs-hidden", !this.thumbnailsVisible);
992
+ if (!this.caption || !this.items[this.currentIndex]) return;
993
+ const item = this.items[this.currentIndex];
994
+ const caption = item.dataset.caption || item.title || "";
995
+ if (caption && this.thumbs.classList.contains("thumbs-hidden")) {
996
+ this.caption.style.bottom = "20px";
997
+ this.caption.style.transition = "bottom 0.3s ease, transform 0.3s ease";
998
+ } else {
999
+ this.caption.style.bottom = "120px";
1000
+ }
1001
+ }
1002
+ }
1003
+ /**
1004
+ * Start drag operation
1005
+ * @param {Event} e - Mouse or touch event
1006
+ */
1007
+ startDrag(e) {
1008
+ const media = this.content.querySelector(".zoomora-media");
1009
+ if (!media || !this.isZoomed) return;
1010
+ this.isDragging = true;
1011
+ media.style.cursor = "grabbing";
1012
+ this.startX = e.clientX || e.touches && e.touches[0].clientX || 0;
1013
+ this.startY = e.clientY || e.touches && e.touches[0].clientY || 0;
1014
+ const transform = media.style.transform;
1015
+ const scaleMatch = transform.match(/scale\(([^)]+)\)/);
1016
+ const translateMatch = transform.match(/translate\(([^,]+),\s*([^)]+)\)/);
1017
+ this.currentScale = scaleMatch ? parseFloat(scaleMatch[1]) : 1;
1018
+ this.currentX = translateMatch ? parseFloat(translateMatch[1]) : 0;
1019
+ this.currentY = translateMatch ? parseFloat(translateMatch[2]) : 0;
1020
+ e.preventDefault();
1021
+ }
1022
+ /**
1023
+ * Handle drag movement
1024
+ * @param {Event} e - Mouse or touch event
1025
+ */
1026
+ drag(e) {
1027
+ if (!this.isDragging) return;
1028
+ e.preventDefault();
1029
+ const clientX = e.clientX || e.touches && e.touches[0].clientX || 0;
1030
+ const clientY = e.clientY || e.touches && e.touches[0].clientY || 0;
1031
+ const deltaX = clientX - this.startX;
1032
+ const deltaY = clientY - this.startY;
1033
+ const newX = this.currentX + deltaX;
1034
+ const newY = this.currentY + deltaY;
1035
+ const media = this.content.querySelector(".zoomora-media");
1036
+ if (!media) return;
1037
+ const mediaRect = media.getBoundingClientRect();
1038
+ const scale = this.currentScale;
1039
+ const maxX = mediaRect.width * (scale - 1) / 2;
1040
+ const maxY = mediaRect.height * (scale - 1) / 2;
1041
+ const constrainedX = Math.max(-maxX, Math.min(maxX, newX));
1042
+ const constrainedY = Math.max(-maxY, Math.min(maxY, newY));
1043
+ media.style.transform = `scale(${scale}) translate(${constrainedX}px, ${constrainedY}px)`;
1044
+ }
1045
+ /**
1046
+ * End drag operation
1047
+ */
1048
+ endDrag() {
1049
+ if (!this.isDragging) return;
1050
+ this.isDragging = false;
1051
+ const media = this.content.querySelector(".zoomora-media");
1052
+ if (media && this.isZoomed) {
1053
+ media.style.cursor = "grab";
1054
+ }
1055
+ }
1056
+ /**
1057
+ * Go to specific item by index
1058
+ * @param {number} index - Item index
1059
+ */
1060
+ goTo(index) {
1061
+ if (index < 0 || index >= this.items.length) return;
1062
+ this.currentIndex = index;
1063
+ this.loadContent();
1064
+ this.updateCounter();
1065
+ this.updateThumbnails();
1066
+ this.updateNavigation();
1067
+ this.updateCaption();
1068
+ }
1069
+ /**
1070
+ * Get current item index
1071
+ * @returns {number} - Current index
1072
+ */
1073
+ getCurrentIndex() {
1074
+ return this.currentIndex;
1075
+ }
1076
+ /**
1077
+ * Get total number of items
1078
+ * @returns {number} - Total items
1079
+ */
1080
+ getTotalItems() {
1081
+ return this.items.length;
1082
+ }
1083
+ /**
1084
+ * Check if lightbox is open
1085
+ * @returns {boolean} - True if open
1086
+ */
1087
+ isOpen() {
1088
+ return this.zoomora && this.zoomora.classList.contains("active");
1089
+ }
1090
+ /**
1091
+ * Update options
1092
+ * @param {Object} newOptions - New options to merge
1093
+ */
1094
+ updateOptions(newOptions) {
1095
+ this.options = __spreadValues(__spreadValues({}, this.options), newOptions);
1096
+ }
1097
+ /**
1098
+ * Refresh the lightbox (re-scan for items)
1099
+ */
1100
+ refresh() {
1101
+ if (this.isOpen()) {
1102
+ const currentItem = this.items[this.currentIndex];
1103
+ if (currentItem) {
1104
+ this.open(currentItem);
1105
+ }
1106
+ }
1107
+ }
1108
+ /**
1109
+ * Destroy the lightbox instance
1110
+ */
1111
+ destroy() {
1112
+ if (!this.isInitialized) return;
1113
+ if (this.isOpen()) {
1114
+ this.close();
1115
+ }
1116
+ this.stopAutoHideTimer();
1117
+ this.eventListeners.forEach((listeners, key) => {
1118
+ listeners.forEach(({ element, event, handler, options }) => {
1119
+ if (element && element.removeEventListener) {
1120
+ element.removeEventListener(event, handler, options);
1121
+ }
1122
+ });
1123
+ });
1124
+ this.eventListeners.clear();
1125
+ if (this.zoomora && this.zoomora.parentNode) {
1126
+ this.zoomora.parentNode.removeChild(this.zoomora);
1127
+ }
1128
+ this.isInitialized = false;
1129
+ this.items = [];
1130
+ this.currentIndex = 0;
1131
+ this.zoomora = null;
1132
+ this.content = null;
1133
+ this.counter = null;
1134
+ this.thumbs = null;
1135
+ this.loading = null;
1136
+ this.caption = null;
1137
+ }
1138
+ }
1139
+ return Zoomora;
1140
+ });
1141
+ });
1142
+ //# sourceMappingURL=zoomora.umd.js.map