zoomora 1.0.0 → 1.2.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.
@@ -24,10 +24,10 @@ var __commonJS = (cb, mod) => function __require() {
24
24
  var require_zoomora_es = __commonJS({
25
25
  "zoomora.es.js"(exports, module) {
26
26
  /*!
27
- * Zoomora Lightbox Plugin v1.0.0
27
+ * Zoomora Lightbox Plugin v1.2.0
28
28
  * A modern, responsive lightbox plugin with zoom, fullscreen, and gallery features
29
29
  *
30
- * Copyright (c) 2025 Faruk Ahmed (FrontTheme)
30
+ * Copyright (c) 2026 Faruk Ahmed (FrontTheme)
31
31
  * Licensed under MIT License
32
32
  */
33
33
  (function(global, factory) {
@@ -61,6 +61,8 @@ var require_zoomora_es = __commonJS({
61
61
  // Zoom step for scroll wheel
62
62
  animationDuration: 300,
63
63
  // Animation duration in ms
64
+ closeOnBackgroundClick: true,
65
+ // Close lightbox when clicking on background
64
66
  onOpen: null,
65
67
  // Callback when lightbox opens
66
68
  onClose: null,
@@ -102,6 +104,7 @@ var require_zoomora_es = __commonJS({
102
104
  this.autoHideDelay = 3e3;
103
105
  this.isControlsVisible = true;
104
106
  this.idleTimer = null;
107
+ this.lastDragTime = 0;
105
108
  this.boundMethods = {
106
109
  handleDocumentClick: this.handleDocumentClick.bind(this),
107
110
  handleKeydown: this.handleKeydown.bind(this),
@@ -154,6 +157,11 @@ var require_zoomora_es = __commonJS({
154
157
  console.warn("Zoomora: Lightbox already exists in DOM");
155
158
  return;
156
159
  }
160
+ const autoHideButtonHTML = this.options.showAutoHideToggle ? `
161
+ <button class="zoomora-btn" id="zoomoraAutoHideBtn" title="Toggle Auto-Hide Controls">
162
+ <i class="zoomorai-controls-off"></i>
163
+ </button>
164
+ ` : "";
157
165
  const zoomoraHTML = `
158
166
  <div class="zoomora" id="zoomoraLightbox">
159
167
  <div class="zoomora-container">
@@ -163,9 +171,7 @@ var require_zoomora_es = __commonJS({
163
171
  <button class="zoomora-btn" id="zoomoraThumbnailBtn" title="Toggle Thumbnails">
164
172
  <i class="zoomorai-thumbnails"></i>
165
173
  </button>
166
- <button class="zoomora-btn" id="zoomoraAutoHideBtn" title="Toggle Auto-Hide Controls">
167
- <i class="zoomorai-controls-off"></i>
168
- </button>
174
+ ${autoHideButtonHTML}
169
175
  <button class="zoomora-btn" id="zoomoraZoomBtn" title="Zoom">
170
176
  <i class="zoomorai-zoom"></i>
171
177
  </button>
@@ -239,10 +245,12 @@ var require_zoomora_es = __commonJS({
239
245
  this.addEventListener(this.content, "touchstart", this.boundMethods.handleTouchStart, { passive: false });
240
246
  this.addEventListener(this.content, "touchmove", this.boundMethods.handleTouchMove, { passive: false });
241
247
  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());
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
+ }
246
254
  }
247
255
  /**
248
256
  * Add event listener with cleanup tracking
@@ -309,11 +317,15 @@ var require_zoomora_es = __commonJS({
309
317
  * Handle fullscreen change events
310
318
  */
311
319
  handleFullscreenChange() {
312
- const isInFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
320
+ const isInFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
313
321
  if (!isInFullscreen && this.isFullscreen) {
314
322
  this.isFullscreen = false;
315
323
  this.zoomora.classList.remove("fullscreen");
316
324
  this.updateButtons();
325
+ } else if (isInFullscreen && !this.isFullscreen) {
326
+ this.isFullscreen = true;
327
+ this.zoomora.classList.add("fullscreen");
328
+ this.updateButtons();
317
329
  }
318
330
  }
319
331
  /**
@@ -321,7 +333,7 @@ var require_zoomora_es = __commonJS({
321
333
  * @param {Event} e - Click event
322
334
  */
323
335
  handleZoomoraClick(e) {
324
- if (e.target === this.zoomora) {
336
+ if (e.target === this.zoomora && this.options.closeOnBackgroundClick) {
325
337
  this.close();
326
338
  }
327
339
  }
@@ -330,9 +342,40 @@ var require_zoomora_es = __commonJS({
330
342
  * @param {Event} e - Click event
331
343
  */
332
344
  handleContentClick(e) {
333
- const media = e.target.closest(".zoomora-media");
334
- if (media && !media.classList.contains("no-zoom") && !this.isDragging) {
335
- this.toggleZoom();
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();
336
379
  }
337
380
  }
338
381
  /**
@@ -572,6 +615,10 @@ var require_zoomora_es = __commonJS({
572
615
  console.error("Zoomora: No source found for item");
573
616
  return;
574
617
  }
618
+ this.isZoomed = false;
619
+ this.currentX = 0;
620
+ this.currentY = 0;
621
+ this.currentScale = 1;
575
622
  this.loading.style.display = "block";
576
623
  if (this.slidesContainer) {
577
624
  this.slidesContainer.innerHTML = "";
@@ -794,6 +841,43 @@ var require_zoomora_es = __commonJS({
794
841
  img.classList.add("no-zoom");
795
842
  }
796
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
+ }
797
881
  /**
798
882
  * Update counter display
799
883
  */
@@ -894,25 +978,53 @@ var require_zoomora_es = __commonJS({
894
978
  */
895
979
  toggleZoom() {
896
980
  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 {
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");
911
1000
  media.style.transform = "";
912
- media.style.cursor = "";
1001
+ media.style.cursor = "pointer";
1002
+ media.classList.remove("zoomed");
913
1003
  this.currentX = 0;
914
1004
  this.currentY = 0;
915
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)`;
916
1028
  }
917
1029
  }
918
1030
  /**
@@ -930,17 +1042,37 @@ var require_zoomora_es = __commonJS({
930
1042
  */
931
1043
  enterFullscreen() {
932
1044
  const element = this.zoomora;
933
- const requestFullscreen = element.requestFullscreen || element.webkitRequestFullscreen || element.mozRequestFullScreen || element.msRequestFullscreen;
934
- if (requestFullscreen) {
935
- requestFullscreen.call(element).then(() => {
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();
936
1057
  this.isFullscreen = true;
937
1058
  this.zoomora.classList.add("fullscreen");
938
1059
  this.updateButtons();
939
- }).catch((err) => {
940
- console.warn("Zoomora: Could not enter fullscreen:", err);
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");
941
1072
  this.fallbackFullscreen();
942
- });
943
- } else {
1073
+ }
1074
+ } catch (error) {
1075
+ console.error("Zoomora: Error entering fullscreen:", error);
944
1076
  this.fallbackFullscreen();
945
1077
  }
946
1078
  }
@@ -948,22 +1080,41 @@ var require_zoomora_es = __commonJS({
948
1080
  * Exit fullscreen mode
949
1081
  */
950
1082
  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(() => {
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(() => {
956
1091
  this.isFullscreen = false;
957
1092
  this.zoomora.classList.remove("fullscreen");
958
1093
  this.updateButtons();
959
1094
  }).catch((err) => {
960
- console.warn("Zoomora: Could not exit fullscreen:", err);
1095
+ console.warn("Zoomora: Exit fullscreen failed:", err.message);
961
1096
  this.fallbackExitFullscreen();
962
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();
963
1113
  } else {
964
1114
  this.fallbackExitFullscreen();
965
1115
  }
966
- } else {
1116
+ } catch (error) {
1117
+ console.error("Zoomora: Error exiting fullscreen:", error);
967
1118
  this.fallbackExitFullscreen();
968
1119
  }
969
1120
  }
@@ -1010,15 +1161,19 @@ var require_zoomora_es = __commonJS({
1010
1161
  const media = this.content.querySelector(".zoomora-media");
1011
1162
  if (!media || !this.isZoomed) return;
1012
1163
  this.isDragging = true;
1164
+ media.classList.add("dragging");
1013
1165
  media.style.cursor = "grabbing";
1014
1166
  this.startX = e.clientX || e.touches && e.touches[0].clientX || 0;
1015
1167
  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;
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
+ }
1022
1177
  e.preventDefault();
1023
1178
  }
1024
1179
  /**
@@ -1028,21 +1183,33 @@ var require_zoomora_es = __commonJS({
1028
1183
  drag(e) {
1029
1184
  if (!this.isDragging) return;
1030
1185
  e.preventDefault();
1186
+ const media = this.content.querySelector(".zoomora-media");
1187
+ if (!media) return;
1031
1188
  const clientX = e.clientX || e.touches && e.touches[0].clientX || 0;
1032
1189
  const clientY = e.clientY || e.touches && e.touches[0].clientY || 0;
1033
1190
  const deltaX = clientX - this.startX;
1034
1191
  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;
1192
+ let newX = this.currentX + deltaX;
1193
+ let newY = this.currentY + deltaY;
1194
+ const container = this.content.getBoundingClientRect();
1039
1195
  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)`;
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;
1046
1213
  }
1047
1214
  /**
1048
1215
  * End drag operation
@@ -1050,9 +1217,13 @@ var require_zoomora_es = __commonJS({
1050
1217
  endDrag() {
1051
1218
  if (!this.isDragging) return;
1052
1219
  this.isDragging = false;
1220
+ this.lastDragTime = Date.now();
1053
1221
  const media = this.content.querySelector(".zoomora-media");
1054
- if (media && this.isZoomed) {
1055
- media.style.cursor = "grab";
1222
+ if (media) {
1223
+ media.classList.remove("dragging");
1224
+ if (this.isZoomed) {
1225
+ media.style.cursor = "grab";
1226
+ }
1056
1227
  }
1057
1228
  }
1058
1229
  /**