Skip to content
68 changes: 65 additions & 3 deletions client-app/shared/catalog/components/image-gallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

<Swiper
class="image-gallery__images"
tabindex="0"
:id="swiperId"
:modules="modules"
:thumbs="{ swiper: thumbsSwiper }"
:breakpoints="{
Expand All @@ -23,10 +25,11 @@
data-te-lightbox-init
@swiper="setImagesSwiper"
@slide-change="setActiveIndex"
@keydown.enter.prevent="openLightbox(swiperId)"
>
<SwiperSlide v-for="(image, i) in images" :key="image.url || i">
<VcImage
class="image-gallery__img"
:class="['image-gallery__img', { 'image-gallery__img--active': activeIndex === i }]"
:src="image.url"
:alt="image.name ?? `${$t('common.accessibility.image')} ${i + 1}`"
:data-te-img="image.url"
Expand All @@ -45,7 +48,17 @@
</div>
</div>

<VcCarouselPagination v-if="showPagination" class="image-gallery__pagination" data-nav-pagination size="sm" />
<VcCarouselPagination
v-if="showPagination"
class="image-gallery__pagination"
data-nav-pagination
size="sm"
tabindex="0"
:id="paginationId"
@keydown.arrow-left.prevent="navigateToPrevious"
@keydown.arrow-right.prevent="navigateToNext"
@keydown.enter.prevent="openLightbox(paginationId)"
/>

<div v-show="showThumbs" class="image-gallery__thumbs-container">
<VcNavButton :label="$t('common.buttons.previous')" size="xs" direction="left" data-nav-prev />
Expand All @@ -62,6 +75,11 @@
:loop="showThumbs && images.length > THUMBS_PER_VIEW"
watch-slides-progress
@swiper="setThumbsSwiper"
tabindex="0"
:id="thumbsId"
@keydown.arrow-left.prevent="navigateToPrevious"
@keydown.arrow-right.prevent="navigateToNext"
@keydown.enter.prevent="openLightbox(thumbsId)"
>
<SwiperSlide v-for="(image, index) in images" :key="index" class="image-gallery__thumb">
<VcImage
Expand All @@ -88,7 +106,7 @@
import { useBreakpoints } from "@vueuse/core";
import { Pagination, Navigation, Thumbs } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import { ref, onMounted, computed, getCurrentInstance, watch, toRef } from "vue";
import { ref, onMounted, computed, getCurrentInstance, watch, toRef, onUnmounted } from "vue";
import { BREAKPOINTS } from "@/core/constants";
import { extractNumberFromString } from "@/core/utilities";
import type { ImageType } from "@/core/api/graphql/types";
Expand All @@ -103,6 +121,9 @@ const props = withDefaults(defineProps<IProps>(), {
});

const componentId = `image-gallery_${getCurrentInstance()!.uid}`;
const swiperId = `swiper_${getCurrentInstance()!.uid}`;
const paginationId = `pagination_${getCurrentInstance()!.uid}`;
const thumbsId = `thumbs_${getCurrentInstance()!.uid}`;

const THUMBS_PER_VIEW = 4;

Expand All @@ -124,6 +145,8 @@ const setThumbsSwiper = (swiper: SwiperCore) => {
thumbsSwiper.value = swiper;
};

const focusedElementBeforeLightboxId = ref<string | null>(null);

const modules = [Pagination, Navigation, Thumbs];

const showThumbs = computed(() => isDesktop.value && props.images?.length > 1);
Expand All @@ -134,6 +157,26 @@ function setActiveIndex() {
activeIndex.value = imagesSwiper.value?.realIndex ?? 0;
}

function navigateToPrevious() {
if (imagesSwiper.value && imagesSwiper.value.allowSlidePrev) {
imagesSwiper.value.slidePrev();
}
}

function navigateToNext() {
if (imagesSwiper.value && imagesSwiper.value.allowSlideNext) {
imagesSwiper.value.slideNext();
}
}

function openLightbox(id: string) {
const currentImage = document.querySelector(`#${componentId} .image-gallery__img--active`) as HTMLElement;
focusedElementBeforeLightboxId.value = id;
if (currentImage) {
currentImage.click();
}
}

watch(
images,
() => {
Expand All @@ -146,6 +189,19 @@ watch(
},
);

function handleLightboxClose() {
if (focusedElementBeforeLightboxId.value) {
const focusedElementBeforeLightbox = document.querySelector(
`#${componentId} #${focusedElementBeforeLightboxId.value}`,
) as HTMLElement;
if (focusedElementBeforeLightbox) {
focusedElementBeforeLightbox.focus();
}
}

focusedElementBeforeLightboxId.value = null;
}

onMounted(async () => {
// Dynamic import is required to avoid SSR errors
// SSR is used to generate routes.json
Expand All @@ -155,6 +211,12 @@ onMounted(async () => {
Lightbox: unknown;
};
initTE({ Lightbox });

document.body.addEventListener("close.te.lightbox", handleLightboxClose);
});

onUnmounted(() => {
document.body.removeEventListener("close.te.lightbox", handleLightboxClose);
});
</script>

Expand Down