@shopware/cms-base-layer 0.0.0-canary-20250116171244

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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/components/SwCategoryNavigation.vue +44 -0
  4. package/components/SwCategoryNavigationLink.vue +57 -0
  5. package/components/SwContactForm.vue +392 -0
  6. package/components/SwListingProductPrice.vue +88 -0
  7. package/components/SwMedia3D.vue +34 -0
  8. package/components/SwNewsletterForm.vue +347 -0
  9. package/components/SwPagination.vue +106 -0
  10. package/components/SwProductAddToCart.vue +93 -0
  11. package/components/SwProductCard.vue +285 -0
  12. package/components/SwProductGallery.vue +39 -0
  13. package/components/SwProductListingFilter.vue +42 -0
  14. package/components/SwProductListingFilters.vue +292 -0
  15. package/components/SwProductPrice.vue +99 -0
  16. package/components/SwProductReviews.vue +99 -0
  17. package/components/SwProductUnits.vue +54 -0
  18. package/components/SwSharedPrice.vue +19 -0
  19. package/components/SwSlider.vue +328 -0
  20. package/components/SwVariantConfigurator.vue +116 -0
  21. package/components/listing-filters/SwFilterPrice.vue +160 -0
  22. package/components/listing-filters/SwFilterProperties.vue +123 -0
  23. package/components/listing-filters/SwFilterRating.vue +101 -0
  24. package/components/listing-filters/SwFilterShippingFree.vue +104 -0
  25. package/components/public/cms/CmsGenericBlock.md +27 -0
  26. package/components/public/cms/CmsGenericBlock.vue +63 -0
  27. package/components/public/cms/CmsGenericElement.md +31 -0
  28. package/components/public/cms/CmsGenericElement.vue +38 -0
  29. package/components/public/cms/CmsNoComponent.vue +27 -0
  30. package/components/public/cms/CmsPage.md +36 -0
  31. package/components/public/cms/CmsPage.vue +65 -0
  32. package/components/public/cms/block/CmsBlockCategoryNavigation.vue +16 -0
  33. package/components/public/cms/block/CmsBlockCenterText.vue +26 -0
  34. package/components/public/cms/block/CmsBlockCrossSelling.vue +15 -0
  35. package/components/public/cms/block/CmsBlockCustomForm.vue +17 -0
  36. package/components/public/cms/block/CmsBlockDefault.vue +14 -0
  37. package/components/public/cms/block/CmsBlockForm.vue +17 -0
  38. package/components/public/cms/block/CmsBlockGalleryBuybox.vue +25 -0
  39. package/components/public/cms/block/CmsBlockImage.vue +16 -0
  40. package/components/public/cms/block/CmsBlockImageBubbleRow.vue +32 -0
  41. package/components/public/cms/block/CmsBlockImageCover.vue +17 -0
  42. package/components/public/cms/block/CmsBlockImageFourColumn.vue +29 -0
  43. package/components/public/cms/block/CmsBlockImageGallery.vue +18 -0
  44. package/components/public/cms/block/CmsBlockImageHighlightRow.vue +27 -0
  45. package/components/public/cms/block/CmsBlockImageSimpleGrid.vue +24 -0
  46. package/components/public/cms/block/CmsBlockImageSlider.vue +17 -0
  47. package/components/public/cms/block/CmsBlockImageText.vue +19 -0
  48. package/components/public/cms/block/CmsBlockImageTextBubble.vue +51 -0
  49. package/components/public/cms/block/CmsBlockImageTextCover.vue +25 -0
  50. package/components/public/cms/block/CmsBlockImageTextGallery.vue +85 -0
  51. package/components/public/cms/block/CmsBlockImageTextRow.vue +43 -0
  52. package/components/public/cms/block/CmsBlockImageThreeColumn.vue +21 -0
  53. package/components/public/cms/block/CmsBlockImageThreeCover.vue +27 -0
  54. package/components/public/cms/block/CmsBlockImageTwoColumn.vue +25 -0
  55. package/components/public/cms/block/CmsBlockProductDescriptionReviews.vue +15 -0
  56. package/components/public/cms/block/CmsBlockProductHeading.vue +26 -0
  57. package/components/public/cms/block/CmsBlockProductListing.vue +17 -0
  58. package/components/public/cms/block/CmsBlockProductSlider.vue +16 -0
  59. package/components/public/cms/block/CmsBlockProductThreeColumn.vue +22 -0
  60. package/components/public/cms/block/CmsBlockSidebarFilter.vue +17 -0
  61. package/components/public/cms/block/CmsBlockText.vue +15 -0
  62. package/components/public/cms/block/CmsBlockTextHero.vue +15 -0
  63. package/components/public/cms/block/CmsBlockTextOnImage.vue +20 -0
  64. package/components/public/cms/block/CmsBlockTextTeaser.vue +16 -0
  65. package/components/public/cms/block/CmsBlockTextTeaserSection.vue +21 -0
  66. package/components/public/cms/block/CmsBlockTextThreeColumn.vue +22 -0
  67. package/components/public/cms/block/CmsBlockTextTwoColumn.vue +28 -0
  68. package/components/public/cms/block/CmsBlockVimeoVideo.vue +17 -0
  69. package/components/public/cms/block/CmsBlockYoutubeVideo.vue +17 -0
  70. package/components/public/cms/element/CmsElementBuyBox.md +1 -0
  71. package/components/public/cms/element/CmsElementBuyBox.vue +190 -0
  72. package/components/public/cms/element/CmsElementCategoryNavigation.md +1 -0
  73. package/components/public/cms/element/CmsElementCategoryNavigation.vue +167 -0
  74. package/components/public/cms/element/CmsElementCrossSelling.md +1 -0
  75. package/components/public/cms/element/CmsElementCrossSelling.vue +106 -0
  76. package/components/public/cms/element/CmsElementCustomForm.md +1 -0
  77. package/components/public/cms/element/CmsElementCustomForm.vue +27 -0
  78. package/components/public/cms/element/CmsElementForm.md +1 -0
  79. package/components/public/cms/element/CmsElementForm.vue +27 -0
  80. package/components/public/cms/element/CmsElementImage.md +1 -0
  81. package/components/public/cms/element/CmsElementImage.vue +105 -0
  82. package/components/public/cms/element/CmsElementImageGallery.md +1 -0
  83. package/components/public/cms/element/CmsElementImageGallery.vue +249 -0
  84. package/components/public/cms/element/CmsElementImageGallery3dPlaceholder.vue +53 -0
  85. package/components/public/cms/element/CmsElementImageSlider.md +1 -0
  86. package/components/public/cms/element/CmsElementImageSlider.vue +29 -0
  87. package/components/public/cms/element/CmsElementManufacturerLogo.md +1 -0
  88. package/components/public/cms/element/CmsElementManufacturerLogo.vue +11 -0
  89. package/components/public/cms/element/CmsElementProductBox.md +1 -0
  90. package/components/public/cms/element/CmsElementProductBox.vue +14 -0
  91. package/components/public/cms/element/CmsElementProductDescriptionReviews.md +1 -0
  92. package/components/public/cms/element/CmsElementProductDescriptionReviews.vue +109 -0
  93. package/components/public/cms/element/CmsElementProductListing.md +1 -0
  94. package/components/public/cms/element/CmsElementProductListing.vue +245 -0
  95. package/components/public/cms/element/CmsElementProductName.md +1 -0
  96. package/components/public/cms/element/CmsElementProductName.vue +10 -0
  97. package/components/public/cms/element/CmsElementProductSlider.md +1 -0
  98. package/components/public/cms/element/CmsElementProductSlider.vue +80 -0
  99. package/components/public/cms/element/CmsElementSidebarFilter.md +1 -0
  100. package/components/public/cms/element/CmsElementSidebarFilter.vue +12 -0
  101. package/components/public/cms/element/CmsElementText.md +1 -0
  102. package/components/public/cms/element/CmsElementText.vue +186 -0
  103. package/components/public/cms/element/CmsElementVimeoVideo.md +1 -0
  104. package/components/public/cms/element/CmsElementVimeoVideo.vue +63 -0
  105. package/components/public/cms/element/CmsElementYoutubeVideo.md +1 -0
  106. package/components/public/cms/element/CmsElementYoutubeVideo.vue +43 -0
  107. package/components/public/cms/section/CmsSectionDefault.md +3 -0
  108. package/components/public/cms/section/CmsSectionDefault.vue +21 -0
  109. package/components/public/cms/section/CmsSectionSidebar.md +3 -0
  110. package/components/public/cms/section/CmsSectionSidebar.vue +49 -0
  111. package/components/public/cms/skeleton/ProductCardSkeleton.vue +44 -0
  112. package/dist/index.d.mts +5 -0
  113. package/dist/index.d.ts +5 -0
  114. package/dist/index.mjs +31 -0
  115. package/helpers/clientOnly.ts +11 -0
  116. package/helpers/html-to-vue/ast.ts +72 -0
  117. package/helpers/html-to-vue/getOptionsFromNode.test.ts +129 -0
  118. package/helpers/html-to-vue/getOptionsFromNode.ts +52 -0
  119. package/helpers/html-to-vue/renderToHtml.ts +45 -0
  120. package/helpers/html-to-vue/renderer.ts +56 -0
  121. package/helpers/media/isSpatial.ts +8 -0
  122. package/index.cjs +7 -0
  123. package/nuxt.config.ts +21 -0
  124. package/package.json +69 -0
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import { useCmsTranslations } from "@shopware/composables";
3
+ import { defu } from "defu";
4
+ import { toRefs } from "vue";
5
+ import { usePrice, useProductPrice } from "#imports";
6
+ import type { Schemas } from "#shopware";
7
+
8
+ const props = defineProps<{
9
+ product: Schemas["Product"];
10
+ }>();
11
+
12
+ type Translations = {
13
+ product: {
14
+ amount: string;
15
+ price: {
16
+ [key: string]: string;
17
+ };
18
+ to: string;
19
+ from: string;
20
+ };
21
+ };
22
+
23
+ let translations: Translations = {
24
+ product: {
25
+ amount: "Amount",
26
+ price: {
27
+ label: "Price",
28
+ to: "To",
29
+ from: "From",
30
+ },
31
+ to: "To",
32
+ from: "From",
33
+ },
34
+ };
35
+
36
+ translations = defu(useCmsTranslations(), translations) as Translations;
37
+
38
+ const { product } = toRefs(props);
39
+
40
+ const { unitPrice, price, tierPrices, isListPrice } = useProductPrice(product);
41
+ const { getFormattedPrice } = usePrice();
42
+ </script>
43
+
44
+ <template>
45
+ <div>
46
+ <div v-if="!tierPrices.length">
47
+ <SwSharedPrice
48
+ v-if="isListPrice"
49
+ class="text-1xl text-gray-900 basis-2/6 justify-end line-through"
50
+ :value="price?.listPrice?.price"
51
+ />
52
+ <SwSharedPrice
53
+ v-if="unitPrice"
54
+ class="text-3xl text-gray-900 basis-2/6 justify-end"
55
+ :class="{
56
+ 'text-red': isListPrice,
57
+ }"
58
+ :value="unitPrice"
59
+ />
60
+ </div>
61
+ <div v-else>
62
+ <table class="border-collapse table-auto w-full text-sm mb-8">
63
+ <thead>
64
+ <tr>
65
+ <th
66
+ class="border-b dark:border-slate-600 font-medium p-4 pl-8 pt-0 pb-3 text-slate-600 dark:text-slate-200 text-left"
67
+ >
68
+ {{ translations.product.amount }}
69
+ </th>
70
+
71
+ <th
72
+ class="border-b dark:border-slate-600 font-medium p-4 pr-8 pt-0 pb-3 text-slate-600 dark:text-slate-200 text-left"
73
+ >
74
+ {{ translations.product.price.label }}
75
+ </th>
76
+ </tr>
77
+ </thead>
78
+ <tbody class="bg-white dark:bg-slate-800">
79
+ <tr v-for="(tierPrice, index) in tierPrices" :key="tierPrice.label">
80
+ <td
81
+ class="border-b border-slate-100 dark:border-slate-700 p-4 pl-8 font-medium text-slate-500 dark:text-slate-400"
82
+ >
83
+ <span v-if="index < tierPrices.length - 1">{{
84
+ translations.product.price.to
85
+ }}</span
86
+ ><span v-else>{{ translations.product.price.from }}</span>
87
+ {{ tierPrice.quantity }}
88
+ </td>
89
+ <td
90
+ class="border-b border-slate-100 dark:border-slate-700 p-4 pr-8 font-medium text-current-500 dark:text-slate-400"
91
+ >
92
+ {{ getFormattedPrice(tierPrice.unitPrice) }}
93
+ </td>
94
+ </tr>
95
+ </tbody>
96
+ </table>
97
+ </div>
98
+ </div>
99
+ </template>
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import { defu } from "defu";
3
+ import { computed, onMounted, ref, toRefs } from "vue";
4
+ import { useCmsTranslations, useProductReviews } from "#imports";
5
+ import type { Schemas } from "#shopware";
6
+
7
+ const props = defineProps<{
8
+ product: Schemas["Product"];
9
+ reviews?: Schemas["ProductReview"][];
10
+ }>();
11
+
12
+ type Translations = {
13
+ product: {
14
+ noReviews: string;
15
+ };
16
+ };
17
+
18
+ let translations: Translations = {
19
+ product: {
20
+ noReviews: "No reviews yet.",
21
+ },
22
+ };
23
+
24
+ translations = defu(useCmsTranslations(), translations) as Translations;
25
+
26
+ const { product, reviews } = toRefs(props);
27
+
28
+ const shouldLoadReviews = !reviews?.value;
29
+
30
+ const loadingReviews = ref<boolean>(shouldLoadReviews);
31
+ const { loadProductReviews, productReviews } = useProductReviews(product);
32
+
33
+ onMounted(async () => {
34
+ if (shouldLoadReviews) {
35
+ await loadProductReviews();
36
+ }
37
+ loadingReviews.value = false;
38
+ });
39
+ const reviewsList = computed<Schemas["ProductReview"][]>(
40
+ () => reviews?.value || productReviews.value || [],
41
+ );
42
+
43
+ const format: Intl.DateTimeFormatOptions = {
44
+ year: "numeric",
45
+ month: "short",
46
+ day: "numeric",
47
+ hour: "numeric",
48
+ minute: "numeric",
49
+ hour12: true,
50
+ };
51
+
52
+ const formatDate = (date: string) =>
53
+ new Date(date).toLocaleDateString("en-us", format);
54
+ </script>
55
+
56
+ <template>
57
+ <div
58
+ v-if="loadingReviews"
59
+ class="absolute inset-0 flex items-center justify-center z-10 bg-white/75"
60
+ >
61
+ <div
62
+ class="h-15 w-15 i-carbon-progress-bar-round animate-spin c-gray-500"
63
+ />
64
+ </div>
65
+ <div v-else-if="reviewsList.length">
66
+ <div v-for="review in reviews" :key="review.id">
67
+ <div
68
+ v-if="review.createdAt"
69
+ class="cms-block-product-description-reviews__reviews-time mt-3 text-gray-600 text-sm"
70
+ >
71
+ <span>{{ formatDate(review.createdAt) }}</span>
72
+ </div>
73
+ <div
74
+ class="cms-block-product-description-reviews__reviews-rating inline-flex items-center mt-2"
75
+ >
76
+ <div
77
+ v-for="_ in review.points"
78
+ :key="`filled-star-${_}`"
79
+ class="w-5 h-5 i-carbon-star-filled"
80
+ ></div>
81
+ <div
82
+ v-for="_ in 5 - (review.points || 0)"
83
+ :key="`empty-star-${_}`"
84
+ class="w-5 h-5 i-carbon-star"
85
+ ></div>
86
+ <div
87
+ class="cms-block-product-description-reviews__reviews-title font-semibold ml-2"
88
+ >
89
+ <p>{{ review.title }}</p>
90
+ </div>
91
+ </div>
92
+ <div class="cms-block-product-description-reviews__reviews-content mt-2">
93
+ <span>{{ review.content }}</span>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <div v-else>{{ translations.product.noReviews }}.</div>
99
+ </template>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import { useCmsTranslations } from "@shopware/composables";
3
+ import { defu } from "defu";
4
+ import { computed } from "vue";
5
+ import type { Schemas } from "#shopware";
6
+
7
+ const props = withDefaults(
8
+ defineProps<{
9
+ product: Schemas["Product"];
10
+ showContent?: boolean;
11
+ }>(),
12
+ {
13
+ showContent: true,
14
+ },
15
+ );
16
+
17
+ type Translations = {
18
+ product: {
19
+ content: string;
20
+ };
21
+ };
22
+
23
+ let translations: Translations = {
24
+ product: {
25
+ content: "Content",
26
+ },
27
+ };
28
+
29
+ translations = defu(useCmsTranslations(), translations) as Translations;
30
+
31
+ const purchaseUnit = computed(() => props.product?.purchaseUnit);
32
+ const unitName = computed(() => props.product?.unit?.translated.name);
33
+ const referencePrice = computed(
34
+ () => props.product?.calculatedPrice?.referencePrice?.price,
35
+ );
36
+ const referenceUnit = computed(
37
+ () => props.product?.calculatedPrice?.referencePrice?.referenceUnit,
38
+ );
39
+ const referenceUnitName = computed(
40
+ () => props.product?.calculatedPrice?.referencePrice?.unitName,
41
+ );
42
+ </script>
43
+
44
+ <template>
45
+ <div v-if="purchaseUnit" class="flex text-gray-500 justify-end gap-1">
46
+ <template v-if="props.showContent">
47
+ {{ translations.product.content }}: {{ purchaseUnit }} {{ unitName }}
48
+ </template>
49
+ <template v-if="referencePrice">
50
+ (<SwSharedPrice :value="referencePrice" /> / {{ referenceUnit }}
51
+ {{ referenceUnitName }} )
52
+ </template>
53
+ </div>
54
+ </template>
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import { usePrice } from "#imports";
4
+
5
+ const { getFormattedPrice } = usePrice();
6
+ const props = defineProps<{
7
+ value: number | undefined;
8
+ }>();
9
+
10
+ const getPrice = computed<string>(() => getFormattedPrice(props.value));
11
+ </script>
12
+
13
+ <template>
14
+ <p class="flex gap-1">
15
+ <slot name="beforePrice" />
16
+ <span>{{ getPrice }}</span>
17
+ <slot name="afterPrice" />
18
+ </p>
19
+ </template>
@@ -0,0 +1,328 @@
1
+ <script setup lang="ts">
2
+ import type { SliderElementConfig } from "@shopware/composables";
3
+ import { useElementSize, useResizeObserver } from "@vueuse/core";
4
+ import {
5
+ computed,
6
+ onBeforeUnmount,
7
+ onMounted,
8
+ ref,
9
+ useSlots,
10
+ useTemplateRef,
11
+ watch,
12
+ } from "vue";
13
+ import type { CSSProperties, VNodeArrayChildren } from "vue";
14
+ import { useCmsElementConfig } from "#imports";
15
+ import type { Schemas } from "#shopware";
16
+
17
+ const props = withDefaults(
18
+ defineProps<{
19
+ config: SliderElementConfig;
20
+ slidesToShow?: number;
21
+ slidesToScroll?: number;
22
+ gap?: string;
23
+ autoplay?: boolean;
24
+ autoplaySpeed?: number;
25
+ }>(),
26
+ {
27
+ slidesToShow: 1,
28
+ slidesToScroll: 1,
29
+ gap: "0px",
30
+ autoplay: false,
31
+ autoplaySpeed: 3000,
32
+ },
33
+ );
34
+
35
+ const { getConfigValue } = useCmsElementConfig({
36
+ config: props.config,
37
+ } as Schemas["CmsSlot"] & {
38
+ config: SliderElementConfig;
39
+ });
40
+
41
+ const slots = useSlots();
42
+ const childrenRaw = computed(
43
+ () => (slots?.default?.()[0].children as VNodeArrayChildren) ?? [],
44
+ );
45
+ const slidesToScroll = computed(() =>
46
+ props.slidesToScroll >= props.slidesToShow
47
+ ? props.slidesToShow
48
+ : props.slidesToScroll,
49
+ );
50
+ const slidesToShow = computed(() =>
51
+ props.slidesToShow >= childrenRaw.value.length
52
+ ? childrenRaw.value.length
53
+ : props.slidesToShow,
54
+ );
55
+ const children = computed<string[]>(() => {
56
+ if (childrenRaw.value.length === 0) return [];
57
+ return [
58
+ ...childrenRaw.value.slice(-slidesToShow.value),
59
+ ...childrenRaw.value,
60
+ ...childrenRaw.value.slice(0, slidesToShow.value),
61
+ ] as string[];
62
+ });
63
+ const emit = defineEmits<(e: "changeSlide", index: number) => void>();
64
+ const slider = useTemplateRef("slider");
65
+ const imageSlider = useTemplateRef("imageSlider");
66
+ const imageSliderTrackStyle = ref<CSSProperties>();
67
+ const activeSlideIndex = ref<number>(0);
68
+ const speed = ref<number>(300);
69
+ const imageSliderTrack = useTemplateRef("imageSliderTrack");
70
+ const autoPlayInterval = ref();
71
+ const isReady = ref<boolean>();
72
+ const isSliding = ref<boolean>();
73
+
74
+ const { width: imageSliderWidth } = useElementSize(imageSlider);
75
+ let timeoutGuard: ReturnType<typeof setTimeout> | undefined;
76
+
77
+ onMounted(() => {
78
+ initSlider();
79
+
80
+ useResizeObserver(slider, () => {
81
+ clearTimeout(timeoutGuard);
82
+ timeoutGuard = setTimeout(() => {
83
+ buildImageSliderTrackStyle(activeSlideIndex.value);
84
+ }, 100);
85
+ });
86
+ });
87
+
88
+ onBeforeUnmount(() => {
89
+ clearInterval(autoPlayInterval.value);
90
+ });
91
+
92
+ watch(
93
+ () => props.autoplay && isReady.value,
94
+ (value) => {
95
+ if (value) {
96
+ autoPlayInterval.value = setInterval(() => {
97
+ next();
98
+ }, props.autoplaySpeed);
99
+ } else {
100
+ if (autoPlayInterval.value) {
101
+ clearInterval(autoPlayInterval.value);
102
+ }
103
+ }
104
+ },
105
+ {
106
+ immediate: true,
107
+ },
108
+ );
109
+
110
+ const imageSliderStyle = computed(() => {
111
+ if (getConfigValue("displayMode") === "cover") {
112
+ return {
113
+ height: getConfigValue("minHeight"),
114
+ margin: `0 -${props.gap}`,
115
+ };
116
+ }
117
+ return {
118
+ minHeight: getConfigValue("minHeight"),
119
+ };
120
+ });
121
+
122
+ const verticalAlignValue = computed(
123
+ () => getConfigValue("verticalAlign") || "flex-start",
124
+ );
125
+ const displayModeValue = computed(
126
+ () => getConfigValue("displayMode") || "standard",
127
+ );
128
+
129
+ const navigationArrowsValue = computed(
130
+ () => props.config?.navigationArrows?.value || "none",
131
+ );
132
+ const navigationDotsValue = computed(
133
+ () => props.config?.navigationDots?.value || "none",
134
+ );
135
+
136
+ function initSlider() {
137
+ if (imageSlider.value) {
138
+ setTimeout(() => {
139
+ buildImageSliderTrackStyle(activeSlideIndex.value, false, undefined);
140
+ isReady.value = true;
141
+ }, 100);
142
+ }
143
+ }
144
+
145
+ function buildImageSliderTrackStyle(
146
+ transformIndex: number,
147
+ moving = false,
148
+ callback = () => {},
149
+ ) {
150
+ let styleObj: CSSProperties = {
151
+ transform: `translate3d(-${
152
+ (transformIndex + slidesToShow.value) *
153
+ (imageSliderWidth.value / slidesToShow.value)
154
+ }px, 0px, 0px)`,
155
+ width: `${children.value.length * imageSliderWidth.value}px`,
156
+ };
157
+
158
+ if (imageSliderTrackStyle.value?.height) {
159
+ styleObj.height = imageSliderTrackStyle.value?.height;
160
+ }
161
+
162
+ if (moving) {
163
+ styleObj = {
164
+ ...styleObj,
165
+ transition: `transform ${speed.value}ms ease 0s`,
166
+ };
167
+ imageSliderTrackStyle.value = { ...styleObj };
168
+ isSliding.value = true;
169
+ setTimeout(() => {
170
+ const { transition: _, ...styleWithoutTransition } = styleObj;
171
+ imageSliderTrackStyle.value = { ...styleWithoutTransition };
172
+ isSliding.value = false;
173
+ callback();
174
+ }, speed.value);
175
+ } else {
176
+ imageSliderTrackStyle.value = { ...styleObj };
177
+ }
178
+
179
+ setTimeout(() => {
180
+ let height = "unset";
181
+ if (displayModeValue.value === "cover") {
182
+ height = "100%";
183
+ } else if (displayModeValue.value === "standard") {
184
+ const childComponent =
185
+ imageSliderTrack.value?.children[transformIndex + 1];
186
+ // If image exist
187
+ height = childComponent?.children[0].children[0].clientHeight
188
+ ? `${childComponent.clientHeight}px`
189
+ : "auto";
190
+ }
191
+ styleObj = {
192
+ ...styleObj,
193
+ height,
194
+ };
195
+ imageSliderTrackStyle.value = { ...styleObj };
196
+ });
197
+ }
198
+
199
+ function next() {
200
+ if (isSliding.value) return;
201
+ activeSlideIndex.value = activeSlideIndex.value + slidesToScroll.value;
202
+ buildImageSliderTrackStyle(activeSlideIndex.value, true, () => {
203
+ if (
204
+ activeSlideIndex.value ===
205
+ children.value.length - slidesToShow.value * 2
206
+ ) {
207
+ activeSlideIndex.value = 0;
208
+ buildImageSliderTrackStyle(activeSlideIndex.value);
209
+ }
210
+ emit("changeSlide", activeSlideIndex.value);
211
+ });
212
+ }
213
+
214
+ function previous() {
215
+ if (isSliding.value) return;
216
+ activeSlideIndex.value = activeSlideIndex.value - slidesToScroll.value;
217
+ buildImageSliderTrackStyle(activeSlideIndex.value, true, () => {
218
+ if (activeSlideIndex.value <= 0 - slidesToShow.value) {
219
+ activeSlideIndex.value = children.value.length - slidesToShow.value * 3;
220
+ buildImageSliderTrackStyle(activeSlideIndex.value);
221
+ }
222
+ emit("changeSlide", activeSlideIndex.value);
223
+ });
224
+ }
225
+
226
+ function goToSlide(index: number) {
227
+ if (isSliding.value) return;
228
+ if (activeSlideIndex.value === index) return;
229
+ activeSlideIndex.value = index;
230
+ buildImageSliderTrackStyle(activeSlideIndex.value, true);
231
+ emit("changeSlide", activeSlideIndex.value);
232
+ }
233
+
234
+ defineExpose({
235
+ next,
236
+ previous,
237
+ goToSlide,
238
+ });
239
+ </script>
240
+ <template>
241
+ <div
242
+ ref="slider"
243
+ :class="{
244
+ 'relative overflow-hidden h-full': true,
245
+ 'px-10': navigationArrowsValue === 'outside',
246
+ 'pb-15': navigationDotsValue === 'outside',
247
+ 'opacity-0': !isReady,
248
+ }"
249
+ >
250
+ <div
251
+ ref="imageSlider"
252
+ class="overflow-hidden h-full"
253
+ :style="imageSliderStyle"
254
+ >
255
+ <div
256
+ ref="imageSliderTrack"
257
+ :class="{
258
+ flex: true,
259
+ 'items-center':
260
+ displayModeValue === 'contain' && verticalAlignValue === 'center',
261
+ 'items-start':
262
+ displayModeValue === 'contain' &&
263
+ verticalAlignValue === 'flex-start',
264
+ 'items-end':
265
+ displayModeValue === 'contain' && verticalAlignValue === 'flex-end',
266
+ }"
267
+ :style="imageSliderTrackStyle"
268
+ >
269
+ <div
270
+ v-for="(child, index) of children"
271
+ :key="index"
272
+ :index="index - slidesToShow"
273
+ :style="{
274
+ width: imageSliderWidth
275
+ ? `${imageSliderWidth / slidesToShow}px`
276
+ : 'auto',
277
+ padding: `0 ${gap}`,
278
+ height: displayModeValue === 'standard' ? 'min-content' : '100%',
279
+ }"
280
+ >
281
+ <component :is="child as any" />
282
+ </div>
283
+ </div>
284
+ </div>
285
+ <div :class="{ hidden: navigationArrowsValue === 'none' }">
286
+ <button
287
+ aria-label="Previous slide"
288
+ :class="{
289
+ 'absolute bg-transparent top-1/2 left-0 transform -translate-y-1/2 py-4': true,
290
+ 'transition bg-white/20 hover:bg-white/50':
291
+ navigationArrowsValue === 'inside',
292
+ }"
293
+ @click="previous"
294
+ >
295
+ <div class="w-15 h-15 i-carbon-chevron-left"></div>
296
+ </button>
297
+ <button
298
+ aria-label="Next slide"
299
+ :class="{
300
+ 'absolute bg-transparent top-1/2 right-0 transform -translate-y-1/2 py-4': true,
301
+ 'transition bg-white/20 hover:bg-white/50':
302
+ navigationArrowsValue === 'inside',
303
+ }"
304
+ @click="next"
305
+ >
306
+ <div class="w-15 h-15 i-carbon-chevron-right"></div>
307
+ </button>
308
+ </div>
309
+ <div
310
+ :class="{
311
+ 'absolute bottom-5 left-1/2 transform -translate-x-1/2 gap-5': true,
312
+ flex: navigationDotsValue !== 'none',
313
+ hidden: navigationDotsValue === 'none',
314
+ }"
315
+ >
316
+ <div
317
+ v-for="(_, i) of childrenRaw"
318
+ :key="`dot-${i}`"
319
+ :class="{
320
+ 'w-5 h-5 rounded-full cursor-pointer': true,
321
+ 'bg-gray-100': i === activeSlideIndex,
322
+ 'bg-gray-500/50': i !== activeSlideIndex,
323
+ }"
324
+ @click="() => goToSlide(i)"
325
+ ></div>
326
+ </div>
327
+ </div>
328
+ </template>