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.
@@ -22,10 +22,10 @@ var __spreadValues = (a, b) => {
22
22
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
23
 
24
24
  /*!
25
- * Zoomora Lightbox Plugin v1.0.0
25
+ * Zoomora Lightbox Plugin v1.2.0
26
26
  * A modern, responsive lightbox plugin with zoom, fullscreen, and gallery features
27
27
  *
28
- * Copyright (c) 2025 Faruk Ahmed (FrontTheme)
28
+ * Copyright (c) 2026 Faruk Ahmed (FrontTheme)
29
29
  * Licensed under MIT License
30
30
  */
31
31
  (function(global, factory) {
@@ -59,6 +59,8 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
59
59
  // Zoom step for scroll wheel
60
60
  animationDuration: 300,
61
61
  // Animation duration in ms
62
+ closeOnBackgroundClick: true,
63
+ // Close lightbox when clicking on background
62
64
  onOpen: null,
63
65
  // Callback when lightbox opens
64
66
  onClose: null,
@@ -100,6 +102,7 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
100
102
  this.autoHideDelay = 3e3;
101
103
  this.isControlsVisible = true;
102
104
  this.idleTimer = null;
105
+ this.lastDragTime = 0;
103
106
  this.boundMethods = {
104
107
  handleDocumentClick: this.handleDocumentClick.bind(this),
105
108
  handleKeydown: this.handleKeydown.bind(this),
@@ -152,6 +155,11 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
152
155
  console.warn("Zoomora: Lightbox already exists in DOM");
153
156
  return;
154
157
  }
158
+ const autoHideButtonHTML = this.options.showAutoHideToggle ? `
159
+ <button class="zoomora-btn" id="zoomoraAutoHideBtn" title="Toggle Auto-Hide Controls">
160
+ <i class="zoomorai-controls-off"></i>
161
+ </button>
162
+ ` : "";
155
163
  const zoomoraHTML = `
156
164
  <div class="zoomora" id="zoomoraLightbox">
157
165
  <div class="zoomora-container">
@@ -161,9 +169,7 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
161
169
  <button class="zoomora-btn" id="zoomoraThumbnailBtn" title="Toggle Thumbnails">
162
170
  <i class="zoomorai-thumbnails"></i>
163
171
  </button>
164
- <button class="zoomora-btn" id="zoomoraAutoHideBtn" title="Toggle Auto-Hide Controls">
165
- <i class="zoomorai-controls-off"></i>
166
- </button>
172
+ ${autoHideButtonHTML}
167
173
  <button class="zoomora-btn" id="zoomoraZoomBtn" title="Zoom">
168
174
  <i class="zoomorai-zoom"></i>
169
175
  </button>
@@ -237,10 +243,12 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
237
243
  this.addEventListener(this.content, "touchstart", this.boundMethods.handleTouchStart, { passive: false });
238
244
  this.addEventListener(this.content, "touchmove", this.boundMethods.handleTouchMove, { passive: false });
239
245
  this.addEventListener(this.content, "touchend", this.boundMethods.handleTouchEnd);
240
- this.addEventListener(document.getElementById("zoomoraAutoHideBtn"), "click", () => this.toggleAutoHide());
241
- this.addEventListener(this.zoomora, "mousemove", () => this.handleUserActivity());
242
- this.addEventListener(this.zoomora, "touchstart", () => this.handleUserActivity());
243
- this.addEventListener(this.zoomora, "keydown", () => this.handleUserActivity());
246
+ if (this.options.showAutoHideToggle) {
247
+ this.addEventListener(document.getElementById("zoomoraAutoHideBtn"), "click", () => this.toggleAutoHide());
248
+ this.addEventListener(this.zoomora, "mousemove", () => this.handleUserActivity());
249
+ this.addEventListener(this.zoomora, "touchstart", () => this.handleUserActivity());
250
+ this.addEventListener(this.zoomora, "keydown", () => this.handleUserActivity());
251
+ }
244
252
  }
245
253
  /**
246
254
  * Add event listener with cleanup tracking
@@ -307,11 +315,15 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
307
315
  * Handle fullscreen change events
308
316
  */
309
317
  handleFullscreenChange() {
310
- const isInFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
318
+ const isInFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
311
319
  if (!isInFullscreen && this.isFullscreen) {
312
320
  this.isFullscreen = false;
313
321
  this.zoomora.classList.remove("fullscreen");
314
322
  this.updateButtons();
323
+ } else if (isInFullscreen && !this.isFullscreen) {
324
+ this.isFullscreen = true;
325
+ this.zoomora.classList.add("fullscreen");
326
+ this.updateButtons();
315
327
  }
316
328
  }
317
329
  /**
@@ -319,7 +331,7 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
319
331
  * @param {Event} e - Click event
320
332
  */
321
333
  handleZoomoraClick(e) {
322
- if (e.target === this.zoomora) {
334
+ if (e.target === this.zoomora && this.options.closeOnBackgroundClick) {
323
335
  this.close();
324
336
  }
325
337
  }
@@ -328,9 +340,40 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
328
340
  * @param {Event} e - Click event
329
341
  */
330
342
  handleContentClick(e) {
331
- const media = e.target.closest(".zoomora-media");
332
- if (media && !media.classList.contains("no-zoom") && !this.isDragging) {
333
- this.toggleZoom();
343
+ if (Date.now() - this.lastDragTime < 200) {
344
+ return;
345
+ }
346
+ if (this.header && this.header.contains(e.target)) {
347
+ return;
348
+ }
349
+ if (e.target.closest("button")) {
350
+ return;
351
+ }
352
+ const mediaElement = e.target.closest(".zoomora-media, .zoomora-video, .zoomora-iframe");
353
+ if (mediaElement) {
354
+ if (mediaElement.classList.contains("zoomora-media") && !mediaElement.classList.contains("no-zoom")) {
355
+ this.toggleZoom();
356
+ }
357
+ return;
358
+ }
359
+ const videoContainer = e.target.closest(".zoomora-video-container");
360
+ if (videoContainer) {
361
+ if (e.target.closest(".zoomora-play-button")) {
362
+ return;
363
+ }
364
+ if (this.options.closeOnBackgroundClick) {
365
+ this.close();
366
+ }
367
+ return;
368
+ }
369
+ const clickedElement = e.target;
370
+ 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";
371
+ if (isBackgroundElement && this.options.closeOnBackgroundClick) {
372
+ this.close();
373
+ return;
374
+ }
375
+ if (this.options.closeOnBackgroundClick) {
376
+ this.close();
334
377
  }
335
378
  }
336
379
  /**
@@ -570,6 +613,10 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
570
613
  console.error("Zoomora: No source found for item");
571
614
  return;
572
615
  }
616
+ this.isZoomed = false;
617
+ this.currentX = 0;
618
+ this.currentY = 0;
619
+ this.currentScale = 1;
573
620
  this.loading.style.display = "block";
574
621
  if (this.slidesContainer) {
575
622
  this.slidesContainer.innerHTML = "";
@@ -792,6 +839,43 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
792
839
  img.classList.add("no-zoom");
793
840
  }
794
841
  }
842
+ /**
843
+ * Calculate zoom levels for an image
844
+ * @param {HTMLImageElement} img - Image element
845
+ * @returns {Object} Zoom configuration
846
+ */
847
+ calculateZoomLevels(img) {
848
+ if (!img || !img.naturalWidth || !img.naturalHeight) {
849
+ return { canZoom: false, levels: [], currentLevel: 0 };
850
+ }
851
+ const container = this.content.getBoundingClientRect();
852
+ const containerWidth = container.width;
853
+ const containerHeight = container.height;
854
+ const naturalWidth = img.naturalWidth;
855
+ const naturalHeight = img.naturalHeight;
856
+ const scaleToFitWidth = containerWidth / naturalWidth;
857
+ const scaleToFitHeight = containerHeight / naturalHeight;
858
+ const fitScale = Math.min(scaleToFitWidth, scaleToFitHeight);
859
+ if (fitScale >= 0.95) {
860
+ return { canZoom: false, levels: [], currentLevel: 0 };
861
+ }
862
+ const levels = [1];
863
+ if (fitScale * 1.5 <= 1) levels.push(1.5);
864
+ if (fitScale * 2 <= 1) levels.push(2);
865
+ if (fitScale * 3 <= 1) levels.push(3);
866
+ const actualSizeScale = 1 / fitScale;
867
+ if (actualSizeScale > levels[levels.length - 1]) {
868
+ levels.push(actualSizeScale);
869
+ }
870
+ return {
871
+ canZoom: levels.length > 1,
872
+ levels,
873
+ currentLevel: 0,
874
+ baseScale: fitScale,
875
+ naturalWidth,
876
+ naturalHeight
877
+ };
878
+ }
795
879
  /**
796
880
  * Update counter display
797
881
  */
@@ -892,25 +976,53 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
892
976
  */
893
977
  toggleZoom() {
894
978
  const media = this.content.querySelector(".zoomora-media");
895
- if (!media || media.tagName !== "IMG" || media.classList.contains("no-zoom")) return;
896
- this.isZoomed = !this.isZoomed;
897
- media.classList.toggle("zoomed", this.isZoomed);
898
- if (this.isZoomed) {
899
- const zoomRatio = parseFloat(media.dataset.zoomRatio) || 2;
900
- let zoomScale = Math.min(zoomRatio, this.options.maxZoomScale);
901
- if (zoomRatio > 2.5) {
902
- zoomScale = Math.min(zoomRatio * 0.7, 2.5);
903
- }
904
- media.style.transform = `scale(${zoomScale})`;
905
- media.style.cursor = "grab";
906
- media.dataset.currentZoomScale = zoomScale;
907
- this.currentScale = zoomScale;
908
- } else {
979
+ if (!media || media.tagName !== "IMG") return;
980
+ const zoomConfig = this.calculateZoomLevels(media);
981
+ if (!zoomConfig.canZoom) {
982
+ return;
983
+ }
984
+ if (!media.dataset.zoomLevel) {
985
+ media.dataset.zoomLevel = "0";
986
+ }
987
+ let currentLevel = parseInt(media.dataset.zoomLevel) || 0;
988
+ currentLevel++;
989
+ if (currentLevel >= zoomConfig.levels.length) {
990
+ currentLevel = 0;
991
+ }
992
+ const targetScale = zoomConfig.levels[currentLevel];
993
+ media.dataset.zoomLevel = currentLevel.toString();
994
+ if (currentLevel === 0) {
995
+ this.isZoomed = false;
996
+ this.isDragging = false;
997
+ media.classList.remove("dragging");
909
998
  media.style.transform = "";
910
- media.style.cursor = "";
999
+ media.style.cursor = "pointer";
1000
+ media.classList.remove("zoomed");
911
1001
  this.currentX = 0;
912
1002
  this.currentY = 0;
913
1003
  this.currentScale = 1;
1004
+ } else {
1005
+ this.isZoomed = true;
1006
+ this.currentScale = targetScale;
1007
+ this.currentX = 0;
1008
+ this.currentY = 0;
1009
+ media.style.transform = `translate(0px, 0px) scale(${targetScale})`;
1010
+ media.style.cursor = "grab";
1011
+ media.classList.add("zoomed");
1012
+ }
1013
+ }
1014
+ /**
1015
+ * Update image transform based on current state
1016
+ * @param {HTMLElement} media - Media element
1017
+ */
1018
+ updateImageTransform(media) {
1019
+ if (!media) return;
1020
+ if (this.currentScale === 1) {
1021
+ media.style.transform = "";
1022
+ } else {
1023
+ const translateX = this.currentX / this.currentScale;
1024
+ const translateY = this.currentY / this.currentScale;
1025
+ media.style.transform = `scale(${this.currentScale}) translate(${translateX}px, ${translateY}px)`;
914
1026
  }
915
1027
  }
916
1028
  /**
@@ -928,17 +1040,37 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
928
1040
  */
929
1041
  enterFullscreen() {
930
1042
  const element = this.zoomora;
931
- const requestFullscreen = element.requestFullscreen || element.webkitRequestFullscreen || element.mozRequestFullScreen || element.msRequestFullscreen;
932
- if (requestFullscreen) {
933
- requestFullscreen.call(element).then(() => {
1043
+ try {
1044
+ if (element.requestFullscreen) {
1045
+ element.requestFullscreen().then(() => {
1046
+ this.isFullscreen = true;
1047
+ this.zoomora.classList.add("fullscreen");
1048
+ this.updateButtons();
1049
+ }).catch((err) => {
1050
+ console.warn("Zoomora: Fullscreen request failed:", err.message);
1051
+ this.fallbackFullscreen();
1052
+ });
1053
+ } else if (element.webkitRequestFullscreen) {
1054
+ element.webkitRequestFullscreen();
934
1055
  this.isFullscreen = true;
935
1056
  this.zoomora.classList.add("fullscreen");
936
1057
  this.updateButtons();
937
- }).catch((err) => {
938
- console.warn("Zoomora: Could not enter fullscreen:", err);
1058
+ } else if (element.mozRequestFullScreen) {
1059
+ element.mozRequestFullScreen();
1060
+ this.isFullscreen = true;
1061
+ this.zoomora.classList.add("fullscreen");
1062
+ this.updateButtons();
1063
+ } else if (element.msRequestFullscreen) {
1064
+ element.msRequestFullscreen();
1065
+ this.isFullscreen = true;
1066
+ this.zoomora.classList.add("fullscreen");
1067
+ this.updateButtons();
1068
+ } else {
1069
+ console.warn("Zoomora: Fullscreen API not supported, using CSS fallback");
939
1070
  this.fallbackFullscreen();
940
- });
941
- } else {
1071
+ }
1072
+ } catch (error) {
1073
+ console.error("Zoomora: Error entering fullscreen:", error);
942
1074
  this.fallbackFullscreen();
943
1075
  }
944
1076
  }
@@ -946,22 +1078,41 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
946
1078
  * Exit fullscreen mode
947
1079
  */
948
1080
  exitFullscreen() {
949
- const isInFullscreen = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
950
- if (isInFullscreen) {
951
- const exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen;
952
- if (exitFullscreen) {
953
- exitFullscreen.call(document).then(() => {
1081
+ const isInFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
1082
+ if (!isInFullscreen) {
1083
+ this.fallbackExitFullscreen();
1084
+ return;
1085
+ }
1086
+ try {
1087
+ if (document.exitFullscreen) {
1088
+ document.exitFullscreen().then(() => {
954
1089
  this.isFullscreen = false;
955
1090
  this.zoomora.classList.remove("fullscreen");
956
1091
  this.updateButtons();
957
1092
  }).catch((err) => {
958
- console.warn("Zoomora: Could not exit fullscreen:", err);
1093
+ console.warn("Zoomora: Exit fullscreen failed:", err.message);
959
1094
  this.fallbackExitFullscreen();
960
1095
  });
1096
+ } else if (document.webkitExitFullscreen) {
1097
+ document.webkitExitFullscreen();
1098
+ this.isFullscreen = false;
1099
+ this.zoomora.classList.remove("fullscreen");
1100
+ this.updateButtons();
1101
+ } else if (document.mozCancelFullScreen) {
1102
+ document.mozCancelFullScreen();
1103
+ this.isFullscreen = false;
1104
+ this.zoomora.classList.remove("fullscreen");
1105
+ this.updateButtons();
1106
+ } else if (document.msExitFullscreen) {
1107
+ document.msExitFullscreen();
1108
+ this.isFullscreen = false;
1109
+ this.zoomora.classList.remove("fullscreen");
1110
+ this.updateButtons();
961
1111
  } else {
962
1112
  this.fallbackExitFullscreen();
963
1113
  }
964
- } else {
1114
+ } catch (error) {
1115
+ console.error("Zoomora: Error exiting fullscreen:", error);
965
1116
  this.fallbackExitFullscreen();
966
1117
  }
967
1118
  }
@@ -1008,15 +1159,19 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
1008
1159
  const media = this.content.querySelector(".zoomora-media");
1009
1160
  if (!media || !this.isZoomed) return;
1010
1161
  this.isDragging = true;
1162
+ media.classList.add("dragging");
1011
1163
  media.style.cursor = "grabbing";
1012
1164
  this.startX = e.clientX || e.touches && e.touches[0].clientX || 0;
1013
1165
  this.startY = e.clientY || e.touches && e.touches[0].clientY || 0;
1014
- const transform = media.style.transform;
1015
- const scaleMatch = transform.match(/scale\(([^)]+)\)/);
1016
- const translateMatch = transform.match(/translate\(([^,]+),\s*([^)]+)\)/);
1017
- this.currentScale = scaleMatch ? parseFloat(scaleMatch[1]) : 1;
1018
- this.currentX = translateMatch ? parseFloat(translateMatch[1]) : 0;
1019
- this.currentY = translateMatch ? parseFloat(translateMatch[2]) : 0;
1166
+ const transform = window.getComputedStyle(media).transform;
1167
+ if (transform && transform !== "none") {
1168
+ const matrix = new DOMMatrix(transform);
1169
+ this.currentX = matrix.m41;
1170
+ this.currentY = matrix.m42;
1171
+ } else {
1172
+ this.currentX = 0;
1173
+ this.currentY = 0;
1174
+ }
1020
1175
  e.preventDefault();
1021
1176
  }
1022
1177
  /**
@@ -1026,21 +1181,33 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
1026
1181
  drag(e) {
1027
1182
  if (!this.isDragging) return;
1028
1183
  e.preventDefault();
1184
+ const media = this.content.querySelector(".zoomora-media");
1185
+ if (!media) return;
1029
1186
  const clientX = e.clientX || e.touches && e.touches[0].clientX || 0;
1030
1187
  const clientY = e.clientY || e.touches && e.touches[0].clientY || 0;
1031
1188
  const deltaX = clientX - this.startX;
1032
1189
  const deltaY = clientY - this.startY;
1033
- const newX = this.currentX + deltaX;
1034
- const newY = this.currentY + deltaY;
1035
- const media = this.content.querySelector(".zoomora-media");
1036
- if (!media) return;
1190
+ let newX = this.currentX + deltaX;
1191
+ let newY = this.currentY + deltaY;
1192
+ const container = this.content.getBoundingClientRect();
1037
1193
  const mediaRect = media.getBoundingClientRect();
1038
- const scale = this.currentScale;
1039
- const maxX = mediaRect.width * (scale - 1) / 2;
1040
- const maxY = mediaRect.height * (scale - 1) / 2;
1041
- const constrainedX = Math.max(-maxX, Math.min(maxX, newX));
1042
- const constrainedY = Math.max(-maxY, Math.min(maxY, newY));
1043
- media.style.transform = `scale(${scale}) translate(${constrainedX}px, ${constrainedY}px)`;
1194
+ const overflowX = Math.max(0, (mediaRect.width - container.width) / 2);
1195
+ const overflowY = Math.max(0, (mediaRect.height - container.height) / 2);
1196
+ if (overflowX > 0) {
1197
+ newX = Math.max(-overflowX, Math.min(overflowX, newX));
1198
+ } else {
1199
+ newX = 0;
1200
+ }
1201
+ if (overflowY > 0) {
1202
+ newY = Math.max(-overflowY, Math.min(overflowY, newY));
1203
+ } else {
1204
+ newY = 0;
1205
+ }
1206
+ media.style.transform = `translate(${newX}px, ${newY}px) scale(${this.currentScale})`;
1207
+ this.startX = clientX;
1208
+ this.startY = clientY;
1209
+ this.currentX = newX;
1210
+ this.currentY = newY;
1044
1211
  }
1045
1212
  /**
1046
1213
  * End drag operation
@@ -1048,9 +1215,13 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
1048
1215
  endDrag() {
1049
1216
  if (!this.isDragging) return;
1050
1217
  this.isDragging = false;
1218
+ this.lastDragTime = Date.now();
1051
1219
  const media = this.content.querySelector(".zoomora-media");
1052
- if (media && this.isZoomed) {
1053
- media.style.cursor = "grab";
1220
+ if (media) {
1221
+ media.classList.remove("dragging");
1222
+ if (this.isZoomed) {
1223
+ media.style.cursor = "grab";
1224
+ }
1054
1225
  }
1055
1226
  }
1056
1227
  /**