fix(extra-features): Fixed the sync product image/gallery

This commit is contained in:
2026-05-29 12:07:12 +02:00
parent d2d1dc4302
commit 1dc5ceb241
4 changed files with 336 additions and 122 deletions
@@ -4,11 +4,18 @@
margin-right: -#{map.get($spacers, 1)}; margin-right: -#{map.get($spacers, 1)};
margin-left: -#{map.get($spacers, 1)}; margin-left: -#{map.get($spacers, 1)};
&.swiper-vertical {
max-height: 600px;
}
&.swiper-horizontal {
max-height: 200px;
}
&__elem { &__elem {
padding: 0 map.get($spacers, 1); padding: 0 map.get($spacers, 1);
cursor: pointer; cursor: pointer;
opacity: .3; opacity: 0.3;
transition: .3s ease opacity; transition: 0.3s ease opacity;
@include make-col(4); @include make-col(4);
&:focus, &:focus,
@@ -1,9 +1,9 @@
class ProductGallery { class ProductGallery {
constructor({ constructor({
thumbsSliderSelector = '.js-product-thumbs', thumbsSliderSelector = ".js-product-thumbs",
mainSliderSelector = '.js-product-main-images', mainSliderSelector = ".js-product-main-images",
modalSliderSelector = '.js-modal-gallery', modalSliderSelector = ".js-modal-gallery",
galleryModalSelector = '.js-product-images-modal', galleryModalSelector = ".js-product-images-modal",
} = {}) { } = {}) {
this.thumbsSliderSelector = thumbsSliderSelector; this.thumbsSliderSelector = thumbsSliderSelector;
this.mainSliderSelector = mainSliderSelector; this.mainSliderSelector = mainSliderSelector;
@@ -20,6 +20,21 @@ class ProductGallery {
this.initModalGallerySlider(); this.initModalGallerySlider();
} }
syncThumbsHeight() {
const mainImages = document.querySelector(".product-main-images");
const thumbs = document.querySelector(this.thumbsSliderSelector);
if (!mainImages || !thumbs) return;
if (thumbs.classList.contains("swiper-vertical")) {
const h = mainImages.offsetHeight;
if (h > 0) {
thumbs.style.height = h + "px";
}
} else {
thumbs.style.height = "";
}
}
async initProductImageSlider() { async initProductImageSlider() {
const thumbsElem = document.querySelector(this.thumbsSliderSelector); const thumbsElem = document.querySelector(this.thumbsSliderSelector);
const galleryTopElem = document.querySelector(this.mainSliderSelector); const galleryTopElem = document.querySelector(this.mainSliderSelector);
@@ -29,25 +44,40 @@ class ProductGallery {
} }
const galleryThumbs = new prestashop.SwiperSlider(thumbsElem, { const galleryThumbs = new prestashop.SwiperSlider(thumbsElem, {
direction: "horizontal", // default
breakpoints: { breakpoints: {
320: { 0: {
direction: "horizontal",
slidesPerView: 3, slidesPerView: 3,
}, },
576: { 768: {
slidesPerView: 4, direction: "vertical",
slidesPerView: 5,
},
992: {
direction: "horizontal",
slidesPerView: 3,
},
1200: {
direction: "vertical",
slidesPerView: 5,
}, },
}, },
watchSlidesVisibility: true, watchSlidesVisibility: true,
watchSlidesProgress: true, watchSlidesProgress: true,
spaceBetween: 10,
}); });
const galleryThumbsInstance = await galleryThumbs.initSlider(); const galleryThumbsInstance = await galleryThumbs.initSlider();
galleryThumbsInstance.on("resize", () => this.syncThumbsHeight());
galleryThumbsInstance.on("breakpoint", () => this.syncThumbsHeight());
const mainSlider = new prestashop.SwiperSlider(galleryTopElem, { const mainSlider = new prestashop.SwiperSlider(galleryTopElem, {
spaceBetween: 10, spaceBetween: 10,
navigation: { navigation: {
nextEl: galleryTopElem.querySelector('.swiper-button-next'), nextEl: galleryTopElem.querySelector(".swiper-button-next"),
prevEl: galleryTopElem.querySelector('.swiper-button-prev'), prevEl: galleryTopElem.querySelector(".swiper-button-prev"),
}, },
thumbs: { thumbs: {
swiper: galleryThumbsInstance, swiper: galleryThumbsInstance,
@@ -57,10 +87,13 @@ class ProductGallery {
const mainSliderInstance = await mainSlider.initSlider(); const mainSliderInstance = await mainSlider.initSlider();
this.mainSliderSwiperInstance = mainSliderInstance; this.mainSliderSwiperInstance = mainSliderInstance;
this.syncThumbsHeight();
} }
initModalGallerySlider() { initModalGallerySlider() {
const gallerySliderElem = document.querySelector(this.modalSliderSelector); const gallerySliderElem = document.querySelector(
this.modalSliderSelector,
);
if (!gallerySliderElem) { if (!gallerySliderElem) {
return; return;
@@ -73,19 +106,33 @@ class ProductGallery {
// DIRTY HACK // DIRTY HACK
setTimeout(() => { setTimeout(() => {
this.modalSliderSwiperInstance.update(); this.modalSliderSwiperInstance.update();
this.modalSliderSwiperInstance.slideTo(this.mainSliderSwiperInstance ? this.mainSliderSwiperInstance.activeIndex : 0, 0); this.modalSliderSwiperInstance.slideTo(
this.mainSliderSwiperInstance
? this.mainSliderSwiperInstance.activeIndex
: 0,
0,
);
gallerySliderElem.style.opacity = 1; gallerySliderElem.style.opacity = 1;
}, 200); }, 200);
} else { } else {
const modalSlider = new prestashop.SwiperSlider(gallerySliderElem, { const modalSlider = new prestashop.SwiperSlider(
gallerySliderElem,
{
slidesPerView: 1, slidesPerView: 1,
spaceBetween: 10, spaceBetween: 10,
initialSlide: this.mainSliderSwiperInstance ? this.mainSliderSwiperInstance.activeIndex : 0, initialSlide: this.mainSliderSwiperInstance
? this.mainSliderSwiperInstance.activeIndex
: 0,
navigation: { navigation: {
nextEl: gallerySliderElem.querySelector('.swiper-button-next'), nextEl: gallerySliderElem.querySelector(
prevEl: gallerySliderElem.querySelector('.swiper-button-prev'), ".swiper-button-next",
),
prevEl: gallerySliderElem.querySelector(
".swiper-button-prev",
),
}, },
}); },
);
const modalSliderInstance = await modalSlider.initSlider(); const modalSliderInstance = await modalSlider.initSlider();
@@ -94,7 +141,7 @@ class ProductGallery {
}; };
// TO REFACTO LATER WITH BS5 REMOVE JQUERY! // TO REFACTO LATER WITH BS5 REMOVE JQUERY!
$(this.galleryModalSelector).on('show.bs.modal', handleModalOpen); $(this.galleryModalSelector).on("show.bs.modal", handleModalOpen);
} }
} }
@@ -0,0 +1,175 @@
//Add functionality to the custom form in product miniatures
import useAlertToast from "@js/theme/components/useAlertToast";
document.addEventListener("DOMContentLoaded", function () {
const toast = useAlertToast();
const parsePositiveInteger = (value, fallback = 1) => {
const parsedValue = parseInt(value, 10);
return Number.isFinite(parsedValue) && parsedValue > 0
? parsedValue
: fallback;
};
const isInvalidStepMultiple = (rawValue, minQuantity, stepQuantity) => {
const parsedValue = parseInt(rawValue, 10);
if (!Number.isFinite(parsedValue) || parsedValue <= 0) {
return false;
}
return (parsedValue - minQuantity) % stepQuantity !== 0;
};
const normalizeQuantityByStep = (rawValue, minQuantity, stepQuantity) => {
const parsedValue = parseInt(rawValue, 10);
const safeValue = Number.isFinite(parsedValue)
? Math.max(parsedValue, minQuantity)
: minQuantity;
return (
minQuantity +
Math.ceil((safeValue - minQuantity) / stepQuantity) * stepQuantity
);
};
const getPackWarningMessage = (input, stepQuantity) => {
const messageTemplate = input.getAttribute(
"data-pack-message-template",
);
if (!messageTemplate) {
return "";
}
return messageTemplate.replace("__PACK__", stepQuantity);
};
let $forms = document.querySelectorAll(".product-miniature__form");
$forms.forEach(($form) => {
let $quantityInput = $form.querySelector("input[name='qty-field']");
let $quantityHidden = $form.querySelector("input[name='qty']");
let $incrementBtn = $form.querySelector(
"button[name='qty-field-plus']",
);
let $decrementBtn = $form.querySelector(
"button[name='qty-field-minus']",
);
if (
!$quantityInput ||
!$quantityHidden ||
!$incrementBtn ||
!$decrementBtn
) {
return;
}
const minQuantity = parsePositiveInteger(
$quantityInput.getAttribute("min"),
parsePositiveInteger($quantityHidden.value, 1),
);
const stepQuantity = parsePositiveInteger(
$quantityInput.getAttribute("step"),
minQuantity,
);
$quantityInput.setAttribute("min", minQuantity);
$quantityInput.setAttribute("step", stepQuantity);
function updateHiddenQuantity() {
const normalizedQuantity = normalizeQuantityByStep(
$quantityInput.value,
minQuantity,
stepQuantity,
);
$quantityHidden.value = normalizedQuantity;
}
function normalizeTypedQuantity(showToast = false) {
const enteredQuantity = $quantityInput.value;
const normalizedQuantity = normalizeQuantityByStep(
enteredQuantity,
minQuantity,
stepQuantity,
);
if (
showToast &&
isInvalidStepMultiple(
enteredQuantity,
minQuantity,
stepQuantity,
)
) {
const warningMessage = getPackWarningMessage(
$quantityInput,
stepQuantity,
);
if (warningMessage) {
toast.warning(warningMessage);
}
}
if (parseInt($quantityInput.value, 10) !== normalizedQuantity) {
$quantityInput.value = normalizedQuantity;
}
$quantityHidden.value = normalizedQuantity;
}
$quantityInput.addEventListener("input", function () {
if ($quantityInput.value === "") {
$quantityHidden.value = minQuantity;
return;
}
updateHiddenQuantity();
});
$incrementBtn.addEventListener("click", function (event) {
event.preventDefault();
const currentValue = normalizeQuantityByStep(
$quantityInput.value,
minQuantity,
stepQuantity,
);
$quantityInput.value = currentValue + stepQuantity;
updateHiddenQuantity();
});
$decrementBtn.addEventListener("click", function (event) {
event.preventDefault();
const currentValue = normalizeQuantityByStep(
$quantityInput.value,
minQuantity,
stepQuantity,
);
$quantityInput.value = Math.max(
minQuantity,
currentValue - stepQuantity,
);
updateHiddenQuantity();
});
$quantityInput.addEventListener("focusout", function () {
normalizeTypedQuantity(true);
});
$quantityInput.addEventListener("keyup", function (event) {
if (event.keyCode === 13) {
normalizeTypedQuantity(true);
}
});
normalizeTypedQuantity(false);
});
});
+1 -16
View File
@@ -13,6 +13,7 @@ import "@js/theme/components/quickview";
import "@js/theme/components/product"; import "@js/theme/components/product";
import "@js/theme/components/cart/cart"; import "@js/theme/components/cart/cart";
import "@js/theme/components/cart/block-cart"; import "@js/theme/components/cart/block-cart";
import "@js/theme/components/product-miniature-form.js";
import usePasswordPolicy from "@js/theme/components/usePasswordPolicy"; import usePasswordPolicy from "@js/theme/components/usePasswordPolicy";
import prestashop from "prestashop"; import prestashop from "prestashop";
@@ -66,22 +67,6 @@ function initStickyHeader() {
} }
} }
// Sync product thumbs height with main images height
function syncThumbsHeight() {
const mainImages = document.querySelector(".product-main-images");
const thumbs = document.querySelector(".product-thumbs.swiper");
// Only apply if Swiper is vertical
if (mainImages && thumbs && thumbs.classList.contains("swiper-vertical")) {
thumbs.style.maxHeight = mainImages.offsetHeight + "px";
thumbs.style.height = mainImages.offsetHeight + "px";
} else {
return;
}
}
window.addEventListener("load", syncThumbsHeight);
window.addEventListener("resize", syncThumbsHeight);
$(() => { $(() => {
initStickyHeader(); initStickyHeader();
accLinksTriggerActive(); accLinksTriggerActive();