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