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