@shopware/cms-base-layer 2.1.0 → 3.0.1

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 (109) hide show
  1. package/README.md +93 -135
  2. package/app/app.config.ts +1 -0
  3. package/app/components/SwCategoryNavigation.vue +1 -0
  4. package/app/components/SwCategoryNavigationLink.vue +16 -8
  5. package/app/components/SwContactForm.vue +1 -0
  6. package/app/components/SwFilterChips.vue +1 -0
  7. package/app/components/SwListingProductPrice.vue +16 -4
  8. package/app/components/SwMedia3D.vue +13 -9
  9. package/app/components/SwNewsletterForm.vue +4 -3
  10. package/app/components/SwProductAddToCart.vue +24 -7
  11. package/app/components/SwProductCard.vue +5 -0
  12. package/app/components/SwProductCardDetails.vue +27 -9
  13. package/app/components/SwProductCardImage.vue +52 -35
  14. package/app/components/SwProductCardSkeleton.vue +37 -14
  15. package/app/components/SwProductGallery.vue +1 -0
  16. package/app/components/SwProductListingFilter.vue +5 -1
  17. package/app/components/SwProductListingFilters.vue +23 -8
  18. package/app/components/SwProductListingFiltersHorizontal.vue +6 -3
  19. package/app/components/SwProductPrice.vue +1 -0
  20. package/app/components/SwProductReviews.vue +14 -6
  21. package/app/components/SwProductReviewsForm.vue +15 -3
  22. package/app/components/SwProductUnits.vue +1 -0
  23. package/app/components/SwQuantitySelect.vue +10 -3
  24. package/app/components/SwSharedPrice.vue +1 -0
  25. package/app/components/SwSlider.vue +1 -0
  26. package/app/components/SwStockInfo.vue +12 -6
  27. package/app/components/SwVariantConfigurator.vue +5 -3
  28. package/app/components/listing-filters/SwFilterPrice.vue +79 -30
  29. package/app/components/listing-filters/SwFilterProperties.vue +40 -25
  30. package/app/components/listing-filters/SwFilterRating.vue +21 -9
  31. package/app/components/listing-filters/SwFilterShippingFree.vue +16 -8
  32. package/app/components/public/cms/CmsBlockSpatialViewer.vue +1 -0
  33. package/app/components/public/cms/CmsGenericBlock.vue +11 -4
  34. package/app/components/public/cms/CmsGenericElement.vue +8 -2
  35. package/app/components/public/cms/CmsNoComponent.vue +88 -8
  36. package/app/components/public/cms/CmsPage.md +1 -1
  37. package/app/components/public/cms/CmsPage.vue +5 -6
  38. package/app/components/public/cms/FrontendAccountCustomerGroupRegistrationPage.vue +52 -0
  39. package/app/components/public/cms/block/CmsBlockCategoryNavigation.vue +1 -0
  40. package/app/components/public/cms/block/CmsBlockCenterText.vue +1 -0
  41. package/app/components/public/cms/block/CmsBlockCrossSelling.vue +1 -0
  42. package/app/components/public/cms/block/CmsBlockCustomForm.vue +1 -0
  43. package/app/components/public/cms/block/CmsBlockForm.vue +1 -0
  44. package/app/components/public/cms/block/CmsBlockGalleryBuybox.vue +4 -1
  45. package/app/components/public/cms/block/CmsBlockImage.vue +1 -0
  46. package/app/components/public/cms/block/CmsBlockImageBubbleRow.vue +4 -1
  47. package/app/components/public/cms/block/CmsBlockImageCover.vue +1 -0
  48. package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +4 -1
  49. package/app/components/public/cms/block/CmsBlockImageGallery.vue +1 -0
  50. package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +1 -0
  51. package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +4 -1
  52. package/app/components/public/cms/block/CmsBlockImageSimpleGrid.vue +1 -0
  53. package/app/components/public/cms/block/CmsBlockImageSlider.vue +1 -0
  54. package/app/components/public/cms/block/CmsBlockImageText.vue +4 -1
  55. package/app/components/public/cms/block/CmsBlockImageTextBubble.vue +4 -1
  56. package/app/components/public/cms/block/CmsBlockImageTextCover.vue +4 -1
  57. package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +4 -12
  58. package/app/components/public/cms/block/CmsBlockImageTextRow.vue +4 -1
  59. package/app/components/public/cms/block/CmsBlockImageThreeColumn.vue +1 -0
  60. package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +4 -1
  61. package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +4 -1
  62. package/app/components/public/cms/block/CmsBlockProductDescriptionReviews.vue +1 -0
  63. package/app/components/public/cms/block/CmsBlockProductHeading.vue +1 -0
  64. package/app/components/public/cms/block/CmsBlockProductListing.vue +1 -0
  65. package/app/components/public/cms/block/CmsBlockProductThreeColumn.vue +1 -0
  66. package/app/components/public/cms/block/CmsBlockSidebarFilter.vue +1 -0
  67. package/app/components/public/cms/block/CmsBlockText.vue +1 -0
  68. package/app/components/public/cms/block/CmsBlockTextHero.vue +1 -0
  69. package/app/components/public/cms/block/CmsBlockTextOnImage.vue +2 -4
  70. package/app/components/public/cms/block/CmsBlockTextThreeColumn.vue +1 -0
  71. package/app/components/public/cms/block/CmsBlockTextTwoColumn.vue +4 -1
  72. package/app/components/public/cms/block/CmsBlockVimeoVideo.vue +1 -0
  73. package/app/components/public/cms/block/CmsBlockYoutubeVideo.vue +1 -0
  74. package/app/components/public/cms/element/CmsElementBuyBox.vue +90 -72
  75. package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +14 -3
  76. package/app/components/public/cms/element/CmsElementCrossSelling.vue +3 -1
  77. package/app/components/public/cms/element/CmsElementCustomForm.vue +1 -0
  78. package/app/components/public/cms/element/CmsElementForm.vue +1 -0
  79. package/app/components/public/cms/element/CmsElementHtml.vue +0 -1
  80. package/app/components/public/cms/element/CmsElementImage.vue +15 -34
  81. package/app/components/public/cms/element/CmsElementImageGallery.vue +6 -2
  82. package/app/components/public/cms/element/CmsElementManufacturerLogo.vue +1 -0
  83. package/app/components/public/cms/element/CmsElementProductBox.vue +1 -0
  84. package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +77 -29
  85. package/app/components/public/cms/element/CmsElementProductListing.vue +22 -7
  86. package/app/components/public/cms/element/CmsElementProductSlider.vue +6 -4
  87. package/app/components/public/cms/element/CmsElementText.vue +2 -0
  88. package/app/components/public/cms/element/CmsElementVimeoVideo.vue +1 -0
  89. package/app/components/public/cms/element/CmsElementYoutubeVideo.vue +1 -0
  90. package/app/components/public/cms/element/SwProductListingPagination.vue +8 -2
  91. package/app/components/public/cms/section/CmsSectionSidebar.vue +13 -8
  92. package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +16 -5
  93. package/app/components/ui/SwitchButton.vue +53 -14
  94. package/app/components/ui/WishlistIcon.vue +8 -2
  95. package/app/composables/useImagePlaceholder.ts +2 -2
  96. package/app/composables/useLcpImagePreload.test.ts +1 -0
  97. package/app/composables/useLcpImagePreload.ts +12 -6
  98. package/app/composables/useTypedAppConfig.ts +16 -0
  99. package/app/helpers/cms/getImageSizes.test.ts +1 -0
  100. package/app/helpers/html-to-vue/ast.ts +1 -0
  101. package/app/helpers/html-to-vue/getOptionsFromNode.test.ts +1 -0
  102. package/app/helpers/html-to-vue/renderToHtml.ts +1 -0
  103. package/app/helpers/html-to-vue/renderer.ts +1 -0
  104. package/app/providers/shopware.test.ts +1 -0
  105. package/index.d.ts +37 -29
  106. package/nuxt.config.ts +5 -0
  107. package/package.json +22 -25
  108. package/app/plugins/unocss-runtime.client.ts +0 -23
  109. package/uno.config.ts +0 -94
@@ -12,6 +12,7 @@ import {
12
12
  import { getCmsTranslate } from "@shopware/helpers";
13
13
  import { defu } from "defu";
14
14
  import { computed, ref, toRef } from "vue";
15
+
15
16
  import {
16
17
  useAddToCart,
17
18
  useCartErrorParamsResolver,
@@ -50,6 +51,8 @@ type Translations = {
50
51
  badges: {
51
52
  topseller: string;
52
53
  };
54
+ addToWishlist: string;
55
+ removeFromWishlist: string;
53
56
  };
54
57
  errors: {
55
58
  [key: string]: string;
@@ -68,6 +71,8 @@ let translations: Translations = {
68
71
  badges: {
69
72
  topseller: "Tip",
70
73
  },
74
+ addToWishlist: "Add to wishlist",
75
+ removeFromWishlist: "Remove from wishlist",
71
76
  },
72
77
  errors: {
73
78
  "product-stock-reached":
@@ -2,6 +2,7 @@
2
2
  import type { BoxLayout } from "@shopware/composables";
3
3
  import type { UrlRouteOutput } from "@shopware/helpers";
4
4
  import { computed } from "vue";
5
+
5
6
  import type { Schemas } from "#shopware";
6
7
 
7
8
  type Translations = {
@@ -28,18 +29,24 @@ const props = defineProps<{
28
29
  const isMinimalLayout = computed(() => props.layoutType === "minimal");
29
30
  </script>
30
31
  <template>
31
- <div class="self-stretch p-2 flex flex-col justify-between items-start gap-4 flex-1">
32
+ <div
33
+ class="self-stretch p-2 flex flex-col justify-between items-start gap-4 flex-1"
34
+ >
32
35
  <div class="self-stretch flex flex-col justify-start items-start gap-4">
33
36
  <div class="self-stretch flex flex-col justify-start items-start gap-2">
34
37
  <div class="self-stretch flex flex-col justify-start items-start gap-1">
35
- <div v-if="productManufacturer"
36
- class="self-stretch text-surface-on-surface text-sm font-bold leading-tight">
38
+ <div
39
+ v-if="productManufacturer"
40
+ class="self-stretch text-surface-on-surface text-sm font-bold leading-tight"
41
+ >
37
42
  {{ productManufacturer }}
38
43
  </div>
39
44
 
40
- <RouterLink :to="productLink"
45
+ <RouterLink
46
+ :to="productLink"
41
47
  class="self-stretch text-surface-on-surface text-2xl font-normal font-serif leading-9 overflow-hidden line-clamp-2 break-words min-h-[4.5rem]"
42
- data-testid="product-box-product-name-link">
48
+ data-testid="product-box-product-name-link"
49
+ >
43
50
  {{ productName }}
44
51
  </RouterLink>
45
52
  </div>
@@ -54,13 +61,24 @@ const isMinimalLayout = computed(() => props.layoutType === "minimal");
54
61
  />
55
62
 
56
63
  <!-- Price for standard layout -->
57
- <SwListingProductPrice v-else :product="product" data-testid="product-box-product-price" />
64
+ <SwListingProductPrice
65
+ v-else
66
+ :product="product"
67
+ data-testid="product-box-product-price"
68
+ />
58
69
  </div>
59
70
 
60
71
  <!-- CTA buttons only for non-minimal layout -->
61
72
  <template v-if="!isMinimalLayout">
62
- <SwBaseButton variant="primary" v-if="!fromPrice" size="medium" :disabled="!product?.available" block
63
- data-testid="add-to-cart-button" @click="addToCartProxy">
73
+ <SwBaseButton
74
+ variant="primary"
75
+ v-if="!fromPrice"
76
+ size="medium"
77
+ :disabled="!product?.available"
78
+ block
79
+ data-testid="add-to-cart-button"
80
+ @click="addToCartProxy"
81
+ >
64
82
  {{ translations.product.addToCart }}
65
83
  </SwBaseButton>
66
84
 
@@ -71,4 +89,4 @@ const isMinimalLayout = computed(() => props.layoutType === "minimal");
71
89
  </RouterLink>
72
90
  </template>
73
91
  </div>
74
- </template>
92
+ </template>
@@ -5,9 +5,9 @@ import {
5
5
  isProductOnSale,
6
6
  isProductTopSeller,
7
7
  } from "@shopware/helpers";
8
- import { useElementSize } from "@vueuse/core";
9
- import { computed, useTemplateRef } from "vue";
10
- import { useImagePlaceholder } from "#imports";
8
+ import { computed, inject } from "vue";
9
+
10
+ import { useUser } from "#imports";
11
11
  import type { Schemas } from "#shopware";
12
12
 
13
13
  type Translations = {
@@ -15,6 +15,8 @@ type Translations = {
15
15
  badges: {
16
16
  topseller: string;
17
17
  };
18
+ addToWishlist: string;
19
+ removeFromWishlist: string;
18
20
  };
19
21
  };
20
22
 
@@ -27,32 +29,13 @@ const props = defineProps<{
27
29
  productLink: UrlRouteOutput;
28
30
  }>();
29
31
 
30
- const containerElement = useTemplateRef<HTMLDivElement>("containerElement");
31
- const { width, height } = useElementSize(containerElement);
32
-
33
- const DEFAULT_THUMBNAIL_SIZE = 10;
34
- function roundUp(num: number) {
35
- return num ? Math.ceil(num / 100) * 100 : DEFAULT_THUMBNAIL_SIZE;
36
- }
37
-
38
32
  const coverSrcPath = computed(() => {
39
33
  return (
40
- getSmallestThumbnailUrl(props.product?.cover?.media) ||
41
- props.product?.cover?.media?.url
34
+ props.product?.cover?.media?.url ||
35
+ getSmallestThumbnailUrl(props.product?.cover?.media)
42
36
  );
43
37
  });
44
38
 
45
- const imageModifiers = computed(() => {
46
- // Use the larger dimension and apply 2x for high-DPI displays
47
- // For square containers, width and height should be the same
48
- const containerSize = Math.max(width.value || 0, height.value || 0);
49
- const size = roundUp(containerSize * 2);
50
- return {
51
- width: size,
52
- height: size,
53
- };
54
- });
55
-
56
39
  const coverAlt = computed(() => {
57
40
  return props.product?.cover?.media?.alt || props.product?.translated?.name;
58
41
  });
@@ -60,31 +43,65 @@ const coverAlt = computed(() => {
60
43
  const isOnSale = computed(() => isProductOnSale(props.product));
61
44
  const isTopseller = computed(() => isProductTopSeller(props.product));
62
45
 
63
- const placeholderSvg = useImagePlaceholder();
46
+ const { isLoggedIn } = useUser();
47
+ const loginModal = inject<{
48
+ open: (options?: { onSuccess?: () => void | Promise<void> }) => void;
49
+ } | null>("loginModal", null);
50
+
51
+ function handleWishlistClick() {
52
+ if (isLoggedIn.value || !loginModal) {
53
+ props.toggleWishlist();
54
+ return;
55
+ }
56
+ loginModal.open({ onSuccess: props.toggleWishlist });
57
+ }
64
58
  </script>
65
59
 
66
60
  <template>
67
- <div ref="containerElement" class="self-stretch min-h-[350px] relative flex flex-col justify-start items-start overflow-hidden aspect-square">
68
- <RouterLink :to="productLink" class="self-stretch h-full relative overflow-hidden">
69
- <NuxtImg preset="productCard"
61
+ <div
62
+ class="self-stretch min-h-[350px] relative flex flex-col justify-start items-start overflow-hidden aspect-square"
63
+ >
64
+ <RouterLink
65
+ :to="productLink"
66
+ class="self-stretch h-full relative overflow-hidden"
67
+ >
68
+ <NuxtImg
69
+ preset="productCard"
70
70
  class="w-full h-full absolute top-0 left-0 object-cover"
71
- :placeholder="placeholderSvg"
72
- :src="coverSrcPath" :alt="coverAlt" :modifiers="imageModifiers" data-testid="product-box-img" />
71
+ :src="coverSrcPath"
72
+ :alt="coverAlt"
73
+ width="400"
74
+ height="400"
75
+ densities="1x"
76
+ loading="lazy"
77
+ data-testid="product-box-img"
78
+ />
73
79
  </RouterLink>
74
80
 
75
- <div v-if="isTopseller || isOnSale"
76
- class="px-1.5 py-1 left-2 bottom-2 absolute bg-other-sale rounded inline-flex justify-center items-center">
81
+ <div
82
+ v-if="isTopseller || isOnSale"
83
+ class="px-1.5 py-1 left-2 bottom-2 absolute bg-other-sale rounded inline-flex justify-center items-center"
84
+ >
77
85
  <div class="text-states-on-error text-xs font-bold leading-none">
78
86
  {{ translations.product.badges.topseller }}
79
87
  </div>
80
88
  </div>
81
89
 
82
90
  <client-only>
83
- <SwIconButton type="secondary" aria-label="Toggle wishlist" :disabled="isLoading"
91
+ <SwIconButton
92
+ type="secondary"
93
+ :aria-label="
94
+ isInWishlist
95
+ ? translations.product.removeFromWishlist
96
+ : translations.product.addToWishlist
97
+ "
98
+ :disabled="isLoading"
84
99
  class="w-10 h-10 right-4 top-4 absolute bg-brand-secondary rounded-full flex items-center justify-center"
85
- data-testid="product-box-toggle-wishlist-button" @click="toggleWishlist">
100
+ data-testid="product-box-toggle-wishlist-button"
101
+ @click="handleWishlistClick"
102
+ >
86
103
  <SwWishlistIcon :filled="isInWishlist" />
87
104
  </SwIconButton>
88
105
  </client-only>
89
106
  </div>
90
- </template>
107
+ </template>
@@ -1,33 +1,56 @@
1
1
  <template>
2
- <div class="inline-flex flex-col items-start justify-start self-stretch overflow-hidden p-px w-full"
3
- aria-hidden="true">
2
+ <div
3
+ class="inline-flex flex-col items-start justify-start self-stretch overflow-hidden p-px w-full"
4
+ aria-hidden="true"
5
+ >
4
6
  <!-- image skeleton -->
5
- <div class="relative flex h-80 flex-col items-start justify-start self-stretch overflow-hidden">
6
- <div class="relative h-80 w-full bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
7
+ <div
8
+ class="relative flex h-80 flex-col items-start justify-start self-stretch overflow-hidden"
9
+ >
10
+ <div
11
+ class="relative h-80 w-full bg-gray-200 dark:bg-gray-700 animate-pulse"
12
+ ></div>
7
13
 
8
14
  <div
9
- class="absolute top-[281px] left-2 inline-flex items-center justify-center rounded bg-gray-300 dark:bg-gray-600 px-3 py-1 animate-pulse">
10
- <span class="h-3 w-16 bg-gray-200 dark:bg-gray-700 rounded block"></span>
15
+ class="absolute top-[281px] left-2 inline-flex items-center justify-center rounded bg-gray-300 dark:bg-gray-600 px-3 py-1 animate-pulse"
16
+ >
17
+ <span
18
+ class="h-3 w-16 bg-gray-200 dark:bg-gray-700 rounded block"
19
+ ></span>
11
20
  </div>
12
21
 
13
- <div class="absolute top-4 right-4 h-10 w-10 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse"></div>
22
+ <div
23
+ class="absolute top-4 right-4 h-10 w-10 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse"
24
+ ></div>
14
25
  </div>
15
26
 
16
27
  <!-- details skeleton -->
17
28
  <div class="flex flex-col items-start justify-start gap-4 self-stretch p-2">
18
- <div class="h-4 w-32 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
29
+ <div
30
+ class="h-4 w-32 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
31
+ ></div>
19
32
 
20
33
  <div class="w-full">
21
- <div class="h-7 w-3/4 rounded bg-gray-200 dark:bg-gray-700 mb-2 animate-pulse"></div>
22
- <div class="h-7 w-1/2 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
34
+ <div
35
+ class="h-7 w-3/4 rounded bg-gray-200 dark:bg-gray-700 mb-2 animate-pulse"
36
+ ></div>
37
+ <div
38
+ class="h-7 w-1/2 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
39
+ ></div>
23
40
  </div>
24
41
 
25
- <div class="h-6 w-24 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
42
+ <div
43
+ class="h-6 w-24 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
44
+ ></div>
26
45
 
27
46
  <div class="flex w-full gap-3">
28
- <div class="flex-1 h-10 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
29
- <div class="w-24 h-10 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
47
+ <div
48
+ class="flex-1 h-10 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
49
+ ></div>
50
+ <div
51
+ class="w-24 h-10 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
52
+ ></div>
30
53
  </div>
31
54
  </div>
32
55
  </div>
33
- </template>
56
+ </template>
@@ -4,6 +4,7 @@ import type {
4
4
  SliderElementConfig,
5
5
  } from "@shopware/composables";
6
6
  import { ref, watch } from "vue";
7
+
7
8
  import type { Schemas } from "#shopware";
8
9
 
9
10
  const { product, config = {} } = defineProps<{
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts" generic="ListingFilter extends { code: string }">
2
2
  import { computed } from "vue";
3
3
  import type { Component } from "vue";
4
+
4
5
  import SwFilterPriceVue from "./listing-filters/SwFilterPrice.vue";
5
6
  import SwFilterPropertiesVue from "./listing-filters/SwFilterProperties.vue";
6
7
  import SwFilterRatingVue from "./listing-filters/SwFilterRating.vue";
@@ -58,7 +59,10 @@ const filterComponent = computed<Component | undefined>(() => {
58
59
  const handleSelectValue = ({
59
60
  code,
60
61
  value,
61
- }: { code: string; value: string | number | boolean }) => {
62
+ }: {
63
+ code: string;
64
+ value: string | number | boolean;
65
+ }) => {
62
66
  emit("filter-change", { code, value });
63
67
  };
64
68
  </script>
@@ -7,8 +7,9 @@ import { useCmsTranslations } from "@shopware/composables";
7
7
  import { defu } from "defu";
8
8
  import { computed, reactive } from "vue";
9
9
  import type { ComputedRef, UnwrapNestedRefs } from "vue";
10
- import { type LocationQueryRaw, useRoute, useRouter } from "vue-router";
11
- import { useCategoryListing } from "#imports";
10
+ import type { LocationQueryRaw } from "vue-router";
11
+
12
+ import { useCategoryListing, useRoute, useRouter } from "#imports";
12
13
  import type { Schemas, operations } from "#shopware";
13
14
 
14
15
  const props = defineProps<{
@@ -268,8 +269,11 @@ const handleRemoveFilterChip = async (chip: {
268
269
  <!-- Filters Header -->
269
270
  <div class="self-stretch flex flex-col justify-start items-start gap-4">
270
271
  <div
271
- class="flex flex-row items-center justify-between w-full mb-4 py-3 border-b border-outline-outline-variant">
272
- <div class="flex-1 text-surface-on-surface text-base font-bold leading-normal">
272
+ class="flex flex-row items-center justify-between w-full mb-4 py-3 border-b border-outline-outline-variant"
273
+ >
274
+ <div
275
+ class="flex-1 text-surface-on-surface text-base font-bold leading-normal"
276
+ >
273
277
  {{ translations.listing.filters }}
274
278
  </div>
275
279
  <SwSortDropdown
@@ -283,7 +287,9 @@ const handleRemoveFilterChip = async (chip: {
283
287
 
284
288
  <!-- Filters List -->
285
289
  <div class="self-stretch flex flex-col justify-start items-start gap-4">
286
- <SwProductListingFilter v-for="filter in getInitialFilters" :key="filter.id"
290
+ <SwProductListingFilter
291
+ v-for="filter in getInitialFilters"
292
+ :key="filter.id"
287
293
  :filter="filter"
288
294
  :selected-manufacturer="sidebarSelectedFilters.manufacturer"
289
295
  :selected-properties="sidebarSelectedFilters.properties"
@@ -292,11 +298,20 @@ const handleRemoveFilterChip = async (chip: {
292
298
  :selected-rating="sidebarSelectedFilters.rating"
293
299
  :selected-shipping-free="sidebarSelectedFilters['shipping-free']"
294
300
  @filter-change="handleFilterChange"
295
- class="w-full" />
301
+ class="w-full"
302
+ />
296
303
  <div v-if="showResetFiltersButton" class="w-full">
297
- <SwBaseButton variant="primary" size="medium" block @click="invokeCleanFilters" type="button">
304
+ <SwBaseButton
305
+ variant="primary"
306
+ size="medium"
307
+ block
308
+ @click="invokeCleanFilters"
309
+ type="button"
310
+ >
298
311
  {{ translations.listing.resetFilters }}
299
- <span class="w-6 h-6 i-carbon-close-filled inline-block align-middle ml-2"></span>
312
+ <span
313
+ class="w-6 h-6 i-carbon-close-filled inline-block align-middle ml-2"
314
+ ></span>
300
315
  </SwBaseButton>
301
316
  </div>
302
317
  </div>
@@ -7,8 +7,9 @@ import { useCmsTranslations } from "@shopware/composables";
7
7
  import { defu } from "defu";
8
8
  import { computed, reactive } from "vue";
9
9
  import type { ComputedRef, UnwrapNestedRefs } from "vue";
10
- import { type LocationQueryRaw, useRoute, useRouter } from "vue-router";
11
- import { useCategoryListing } from "#imports";
10
+ import type { LocationQueryRaw } from "vue-router";
11
+
12
+ import { useCategoryListing, useRoute, useRouter } from "#imports";
12
13
  import type { Schemas, operations } from "#shopware";
13
14
 
14
15
  const props = defineProps<{
@@ -299,7 +300,9 @@ const hasActiveFilter = (filter: { code: string }) => {
299
300
  type="button"
300
301
  >
301
302
  {{ translations.listing.resetFilters }}
302
- <span class="w-5 h-5 i-carbon-close inline-block align-middle ml-1"></span>
303
+ <span
304
+ class="w-5 h-5 i-carbon-close inline-block align-middle ml-1"
305
+ ></span>
303
306
  </SwBaseButton>
304
307
  </div>
305
308
  </div>
@@ -2,6 +2,7 @@
2
2
  import { useCmsTranslations } from "@shopware/composables";
3
3
  import { defu } from "defu";
4
4
  import { toRefs } from "vue";
5
+
5
6
  import { usePrice, useProductPrice } from "#imports";
6
7
  import type { Schemas } from "#shopware";
7
8
 
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { defu } from "defu";
3
3
  import { computed, onMounted, ref, toRefs } from "vue";
4
+
4
5
  import {
5
6
  useCmsTranslations,
6
7
  useProductReviews,
@@ -77,7 +78,10 @@ const formatDate = (date: string) => {
77
78
  v-for="(review, index) in reviewsList"
78
79
  :key="review.id"
79
80
  class="pb-6"
80
- :class="{ 'border-b border-surface-surface-container-highest': index < reviewsList.length - 1 }"
81
+ :class="{
82
+ 'border-b border-surface-surface-container-highest':
83
+ index < reviewsList.length - 1,
84
+ }"
81
85
  >
82
86
  <div
83
87
  v-if="review.createdAt"
@@ -87,19 +91,23 @@ const formatDate = (date: string) => {
87
91
  <span>{{ formatDate(review.createdAt) }}</span>
88
92
  </div>
89
93
  <div
90
- v-if="!review.status"
91
- class="mt-2 text-3 p-2 bg-states-info-container text-states-on-info-container flex gap-2 items-center"
92
- >
94
+ v-if="!review.status"
95
+ class="mt-2 text-3 p-2 bg-states-info-container text-states-on-info-container flex gap-2 items-center"
96
+ >
93
97
  <div class="w-6 h-6 i-carbon-warning" />
94
98
  {{ translations.product.reviewNotAccepted }}
95
99
  </div>
96
- <div class="cms-block-product-description-reviews__reviews-rating inline-flex items-center mt-2">
100
+ <div
101
+ class="cms-block-product-description-reviews__reviews-rating inline-flex items-center mt-2"
102
+ >
97
103
  <SwProductRating
98
104
  :rating="review.points ?? 0"
99
105
  :star-size="20"
100
106
  :show-count="false"
101
107
  />
102
- <div class="cms-block-product-description-reviews__reviews-title font-semibold ml-2">
108
+ <div
109
+ class="cms-block-product-description-reviews__reviews-title font-semibold ml-2"
110
+ >
103
111
  <p>{{ review.title }}</p>
104
112
  </div>
105
113
  </div>
@@ -7,6 +7,7 @@ import type { ValidationRuleWithoutParams } from "@vuelidate/core";
7
7
  import { minLength, required } from "@vuelidate/validators";
8
8
  import { defu } from "defu";
9
9
  import { computed, reactive, ref } from "vue";
10
+
10
11
  import { useShopwareContext } from "#imports";
11
12
 
12
13
  const props = defineProps<{
@@ -154,7 +155,10 @@ const invokeRating = (value: number) => {
154
155
  </script>
155
156
 
156
157
  <template>
157
- <form class="flex flex-col gap-4 md:gap-5 relative" @submit.prevent="invokeSend">
158
+ <form
159
+ class="flex flex-col gap-4 md:gap-5 relative"
160
+ @submit.prevent="invokeSend"
161
+ >
158
162
  <div
159
163
  v-if="isLoading"
160
164
  class="absolute inset-0 flex items-center justify-center z-10 bg-surface-surface/80 rounded-md"
@@ -171,7 +175,11 @@ const invokeRating = (value: number) => {
171
175
  <span class="text-sm text-surface-on-surface-variant">{{
172
176
  translations.product.reviewsForm.rating
173
177
  }}</span>
174
- <div class="flex flex-row gap-2" role="group" :aria-label="translations.product.reviewsForm.rating">
178
+ <div
179
+ class="flex flex-row gap-2"
180
+ role="group"
181
+ :aria-label="translations.product.reviewsForm.rating"
182
+ >
175
183
  <SwStarIcon
176
184
  v-for="index in state.rating || 0"
177
185
  :key="`filled-${index}`"
@@ -217,7 +225,11 @@ const invokeRating = (value: number) => {
217
225
  <SwExclamationIcon :size="20" />
218
226
  </div>
219
227
  <div class="flex-1">
220
- <p v-for="(error, index) in errorMessages" :key="index" class="text-sm text-states-error">
228
+ <p
229
+ v-for="(error, index) in errorMessages"
230
+ :key="index"
231
+ class="text-sm text-states-error"
232
+ >
221
233
  {{ error.detail }}
222
234
  </p>
223
235
  </div>
@@ -2,6 +2,7 @@
2
2
  import { useCmsTranslations } from "@shopware/composables";
3
3
  import { defu } from "defu";
4
4
  import { computed } from "vue";
5
+
5
6
  import type { Schemas } from "#shopware";
6
7
 
7
8
  const { product, showContent = true } = defineProps<{
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { defu } from "defu";
3
3
  import { computed, useId } from "vue";
4
+
4
5
  import { useCmsTranslations } from "#imports";
5
6
 
6
7
  type Translations = {
@@ -63,7 +64,9 @@ const sizeClasses = {
63
64
  };
64
65
  </script>
65
66
  <template>
66
- <div class="rounded outline outline-1 outline-offset-[-1px] outline-outline-outline inline-flex">
67
+ <div
68
+ class="rounded outline outline-1 outline-offset-[-1px] outline-outline-outline inline-flex"
69
+ >
67
70
  <button
68
71
  type="button"
69
72
  :class="sizeClasses[size]"
@@ -73,9 +76,13 @@ const sizeClasses = {
73
76
  >
74
77
  -
75
78
  </button>
76
- <div class="bg-white border-l border-r border-outline-outline inline-flex flex-col justify-center items-center">
79
+ <div
80
+ class="bg-white border-l border-r border-outline-outline inline-flex flex-col justify-center items-center"
81
+ >
77
82
  <!-- visually hidden label for screen readers -->
78
- <label :for="inputId" class="sr-only">{{ translations.form.quantitySelect.label }}</label>
83
+ <label :for="inputId" class="sr-only">{{
84
+ translations.form.quantitySelect.label
85
+ }}</label>
79
86
 
80
87
  <input
81
88
  :id="inputId"
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from "vue";
3
+
3
4
  import { usePrice } from "#imports";
4
5
 
5
6
  const { getFormattedPrice } = usePrice();
@@ -11,6 +11,7 @@ import {
11
11
  watch,
12
12
  } from "vue";
13
13
  import type { CSSProperties, VNode, VNodeArrayChildren } from "vue";
14
+
14
15
  import { useCmsElementConfig, useHead, useId } from "#imports";
15
16
  import type { Schemas } from "#shopware";
16
17
 
@@ -31,14 +31,20 @@ translations = defu(useCmsTranslations(), translations) as Translations;
31
31
  </script>
32
32
  <template>
33
33
  <div class="inline-flex justify-start items-center gap-2">
34
- <div class="w-2 h-2 bg-states-success rounded-full" v-if="availableStock > 0"></div>
34
+ <div
35
+ class="w-2 h-2 bg-states-success rounded-full"
36
+ v-if="availableStock > 0"
37
+ ></div>
35
38
  <div class="w-2 h-2 bg-states-error rounded-full" v-else></div>
36
- <span v-if="availableStock >= minPurchase && deliveryTime">{{ translations.product.deliveryTime }} {{
37
- deliveryTime?.name }}
39
+ <span v-if="availableStock >= minPurchase && deliveryTime"
40
+ >{{ translations.product.deliveryTime }} {{ deliveryTime?.name }}
38
41
  </span>
39
- <span v-else-if="availableStock < minPurchase && deliveryTime && restockTime">
42
+ <span
43
+ v-else-if="availableStock < minPurchase && deliveryTime && restockTime"
44
+ >
40
45
  {{ translations.product.deliveryTime }} {{ restockTime }}
41
- {{ translations.product.days }} {{ deliveryTime?.name }}</span>
46
+ {{ translations.product.days }} {{ deliveryTime?.name }}</span
47
+ >
42
48
  <span v-else>{{ translations.product.noAvailable }}</span>
43
49
  </div>
44
- </template>
50
+ </template>
@@ -4,8 +4,8 @@ import { buildUrlPrefix, getProductRoute } from "@shopware/helpers";
4
4
  import { defu } from "defu";
5
5
  import { computed, ref, unref } from "vue";
6
6
  import type { ComputedRef } from "vue";
7
- import { useRouter } from "vue-router";
8
- import { useProductConfigurator, useUrlResolver } from "#imports";
7
+
8
+ import { useProductConfigurator, useRouter, useUrlResolver } from "#imports";
9
9
  import type { Schemas } from "#shopware";
10
10
 
11
11
  const { getUrlPrefix } = useUrlResolver();
@@ -85,7 +85,9 @@ const onHandleChange = async () => {
85
85
  :key="optionGroup.id"
86
86
  class="mt-6"
87
87
  >
88
- <div class="text-sm text-gray-900 font-medium">{{ optionGroup.name }}</div>
88
+ <div class="text-sm text-gray-900 font-medium">
89
+ {{ optionGroup.name }}
90
+ </div>
89
91
  <fieldset class="mt-4 flex-1">
90
92
  <legend class="sr-only">
91
93
  {{ translations.product.chooseA }} {{ optionGroup.name }}