# v1.2.0 release notes

# Introduction

In this release, we:

  • upgraded @vue-storefront/core package to version 2.5.12, which added an optional middlewareUrl property to define the URL of the Server Middleware. See its migration guide (opens new window) for more information. Cache control is no longer included into @vue-storefront/core package. To use caching see tips and tricks.
  • upgraded @storefront-ui/vue package to version 0.13.0, which removed mobileObserver and SfSlidingSection and requires adding type="submit" to SfButtons in forms.
  • fixed an issue with SfSearchBar and @keydown.enter

# Migration guide

# Migrating to @vue-storefront/core with v2.5.12

# nuxt.config.js

publicRuntimeConfig: {
  theme,
+ middlewareUrl:
+   process.env.NODE_ENV === 'production'
+     ? process.env.API_BASE_URL
+     : 'http://localhost:3000/api/'
},
# .env
+ API_BASE_URL=<URL to the Server Middleware>

# Migrating to @storefront-ui/vue with 0.13.0

# Exclude core-js package

Exclude the core-js package from Babel transpilation, as shown below:

# nuxt.config.js

babel: {
  plugins: [
    [
      '@babel/plugin-proposal-private-methods',
      { loose: true }
    ]
  ],
+ exclude: [/\bcore-js\b/, /\bwebpack\/buildin\b/]
},
# pages/Home.vue
# pages/Category.vue
# pages/Product.vue

- middleware: cacheControl({
-   'max-age': 60,
-   'stale-when-revalidate': 5
- }),

# Update SfButton

The SfButton component used in forms now requires the type="submit" attribute. The following components use it in the default theme:

  • AddReview component
  • ReserPasswordForm component
  • ProfileUpdateForm component
  • MyNewsletter page
<form>
- <SfButton>
+ <SfButton type="submit">
    Submit
  </SfButton>
</form>

# Remove mobileObserver

# components/AppHeader

<template #navigation>
- <HeaderNavigation :categories="navigation" :isMobile="isMobile" />
+ <HeaderNavigation :categories="navigation" />
</template>

...

- import {
-   mapMobileObserver,
-   unMapMobileObserver
- } from '@storefront-ui/vue/src/utilities/mobile-observer.js';

...

- const isMobile = ref(mapMobileObserver().isMobile.get());

...

- const closeOrFocusSearchBar = () => {
-   if (isMobile.value) {
-     return closeSearch();
-   } else {
-     term.value = '';
-     return searchBarRef.value.$el.children[0].focus();
-   }
+ const clearAndFocusSearchBar = () => {
+   term.value = '';
+   return searchBarRef.value.$el.children[0]?.children[0]?.focus();

...

const onSearchBarIconClick = computed(() =>
  term.value
-   ? closeOrFocusSearchBar
+   ? clearAndFocusSearchBar
    : () => {
      isSearchOpen.value
        ? (isSearchOpen.value = false)

...

- watch(
-   () => term.value,
-   (newVal, oldVal) => {
-     const shouldSearchBeOpened =
-       !isMobile.value &&
-       term.value.length > 0 &&
-       ((!oldVal && newVal) ||
-         (newVal.length !== oldVal.length && isSearchOpen.value === false));
-     if (shouldSearchBeOpened) {
-       isSearchOpen.value = true;
-     }
-   }
- );
- onBeforeUnmount(() => {
-   unMapMobileObserver();
- });

...

return {
  accountIcon,
  cartTotalItems,
  handleAccountClick,
  toggleCartSidebar,
  toggleWishlistSidebar,
  term,
  isSearchOpen,
  isCheckoutPage,
  closeSearch,
  handleSearch,
  loading,
  result,
- closeOrFocusSearchBar,
+ clearAndFocusSearchBar,
  searchBarRef,
- isMobile,
  isMobileMenuOpen,
  products,
  navigation,
  wishlistTotalItems,
  searchBarIcon,
  onSearchBarIconClick
};
# components/HeaderNavigation.vue

<template>
- <div class="sf-header__navigation desktop" v-if="!isMobile">
-   <SfHeaderNavigationItem
-     v-for="(category, key) in categories"
-     :key="key"
-     class="nav-item"
-     v-e2e="`app-header-url_${category.slug}`"
-     :label="category.label"
-     :link="localePath(`/c${category.slug}`)"
-   />
- </div>
- <SfModal v-else :visible="isMobileMenuOpen">
+ <div>
+   <div class="sf-header__navigation desktop">
+     <SfHeaderNavigationItem
+       v-for="(category, key) in categories"
+       :key="key"
+       class="nav-item"
+       v-e2e="`app-header-url_${category.slug}`"
+       :label="category.label"
+       :link="localePath(`/c${category.slug}`)"
+     />
+   </div>
+   <SfModal :visible="isMobileMenuOpen" class="smartphone-only">
      <SfHeaderNavigationItem
        v-for="(category, key) in categories"
        :key="key"
        class="nav-item"
        v-e2e="`app-header-url_${category.slug}`"
      >
        <template #desktop-navigation-item>
          <SfMenuItem
            :label="category.label"
            class="sf-header-navigation-item__menu-item"
            @click="navigate(`/c${category.slug}`)"
          />
        </template>
        <template #mobile-navigation-item>
          <SfMenuItem
            :label="category.label"
            class="sf-header-navigation-item__menu-item"
            @click="navigate(`/c${category.slug}`)"
          />
        </template>
      </SfHeaderNavigationItem>
    </SfModal>
+ </div>
</template>

...

props: {
- isMobile: {
-   type: Boolean,
-   default: false
- },
  categories: {
    type: Array as PropType<Array<SearchResultNavigationItem>>,
    default: () => []
  }
},
# components/RelatedProducts.vue

- :imageWidth="isMobile ? 154 : 216"
- :imageHeight="isMobile ? 154 : 216"
+ :imageWidth="216"
+ :imageHeight="216"

...

- import {
-   mapMobileObserver,
-   unMapMobileObserver
- } from '@storefront-ui/vue/src/utilities/mobile-observer.js';

...

import {
  defineComponent,
- onBeforeUnmount,
- PropType,
- ref
+ PropType
} from '@nuxtjs/composition-api';

...

- const isMobile = ref(mapMobileObserver().isMobile.get());

...

- onBeforeUnmount(() => {
-   unMapMobileObserver();
- });

return {
  productData,
  wishlistHelpers,
  wishlist,
  isInWishlist,
  addItemToWishlist,
  removeItemFromWishlist,
  isInCart,
  addItemToCart,
- getPurchasableDefaultVariant,
- isMobile
+ getPurchasableDefaultVariant
};

...

&__item {
    margin: 1.9375rem 0 2.4375rem 0;
  }
+
+ ::v-deep .sf-product-card__image .sf-image {
+   --image-width:  9.625rem;
+   --image-height:  9.625rem;
+     @include for-desktop {
+       --image-width:  13.5rem;
+       --image-height:  13.5rem;
+     }
+ }
}
</style>
# layouts/error.vue

- :width="isMobile ? 230 : 412"
- :height="isMobile ? 230 : 412"
+ :width="412"
+ :height="412"

...

- import { computed, useRouter } from '@nuxtjs/composition-api';
+ import { useRouter } from '@nuxtjs/composition-api';

-import { mapMobileObserver } from '@storefront-ui/vue/src/utilities/mobile-observer.js';

...

- const isMobile = computed(() => mapMobileObserver().isMobile.get());

return {
  router,
- isMobile,
  addBasePath
};

...

- .image {
+
+ ::v-deep .sf-image {
  --image-width: 14.375rem;
+ --image-height: 14.375rem;
  padding: var(--spacer-xl) 0;
  @include for-desktop {
    --image-width: 25.75rem;
+   --image-height: 25.75rem;
  }
}
# pages/Category.vue

- :imageWidth="isMobile ? 150 : 216"
- :imageHeight="isMobile ? 150 : 216"
+ :imageWidth="216"
+ :imageHeight="216"

...

- :imageWidth="isMobile ? 85 : 140"
- :imageHeight="isMobile ? 113 : 200"
+ :imageWidth="140"
+ :imageHeight="200"

...

import {
  computed,
  defineComponent,
- onBeforeUnmount,
  ref,
  useContext,
  useMeta
} from '@nuxtjs/composition-api';
- import {
-   mapMobileObserver,
-   unMapMobileObserver
- } from '@storefront-ui/vue/src/utilities/mobile-observer.js';

...

- import cacheControl from './../helpers/cacheControl';

...

- const isMobile = ref(mapMobileObserver().isMobile.get());

...

- onBeforeUnmount(() => {
-   unMapMobileObserver();
- });

...

&__product-card {
  --product-card-title-margin: var(--spacer-base) 0 0 0;
  --product-card-title-font-weight: var(--font-weight--medium);
  --product-card-title-margin: var(--spacer-xs) 0 0 0;
  flex: 1 1 50%;
  @include for-desktop {
    --product-card-title-font-weight: var(--font-weight--normal);
    --product-card-add-button-bottom: var(--spacer-base);
    --product-card-title-margin: var(--spacer-sm) 0 0 0;
  }
+ ::v-deep .sf-image {
+   --image-width: 9.375rem;
+   --image-height: 9.375rem;
+   @include for-desktop {
+     --image-width: 13.5rem;
+     --image-height: 13.5rem;
+   }
+ }
}
&__product-card-horizontal {
  flex: 0 0 100%;
- @include for-mobile {
-   ::v-deep .sf-image {
-     --image-width: 5.3125rem;
-     --image-height: 7.0625rem;
+ ::v-deep .sf-image {
+   --image-width: 5.3125rem;
+   --image-height: 7.0625rem;
+   @include for-desktop {
+     --image-width: 8.75rem;
+     --image-height: 12.5rem;
    }
  }
}

# Fixing SfSearchBar component

# components/AppHeader.vue
<SfSearchBar
  ref="searchBarRef"
  :placeholder="$t('Search for items')"
  aria-label="Search"
  class="sf-header__search"
  :class="{ 'search-hidden': isCheckoutPage }"
  :value="term"
  @input="handleSearch"
-   @keydown.enter="handleSearch($event)"
+   @keyup.enter="handleSearch($event)"
  @focus="isSearchOpen = true"
  @keydown.esc="closeSearch"
  :icon="searchBarIcon"
  @click:icon="onSearchBarIconClick"
/>