@shopify/hydrogen 1.0.1 → 1.0.2

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 (40) hide show
  1. package/dist/esnext/client.d.ts +4 -4
  2. package/dist/esnext/client.js +4 -4
  3. package/dist/esnext/components/AddToCartButton/AddToCartButton.client.js +1 -1
  4. package/dist/esnext/components/Image/Image.js +22 -13
  5. package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.d.ts +1 -1
  6. package/dist/esnext/components/LocalizationProvider/LocalizationClientProvider.client.js +1 -1
  7. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.d.ts +3 -2
  8. package/dist/esnext/components/LocalizationProvider/LocalizationProvider.server.js +6 -10
  9. package/dist/esnext/components/ProductOptionsProvider/context.d.ts +1 -1
  10. package/dist/esnext/components/ProductOptionsProvider/index.d.ts +0 -1
  11. package/dist/esnext/components/ProductOptionsProvider/index.js +0 -1
  12. package/dist/esnext/components/index.d.ts +1 -2
  13. package/dist/esnext/components/index.js +1 -2
  14. package/dist/esnext/foundation/Analytics/connectors/Shopify/ShopifyAnalytics.client.js +1 -1
  15. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.d.ts +1 -2
  16. package/dist/esnext/foundation/HydrogenRequest/HydrogenRequest.server.js +1 -1
  17. package/dist/esnext/foundation/ServerPropsProvider/index.d.ts +1 -2
  18. package/dist/esnext/foundation/ServerPropsProvider/index.js +1 -1
  19. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.client.d.ts +4 -2
  20. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.client.js +4 -2
  21. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.d.ts +4 -2
  22. package/dist/esnext/foundation/ShopifyProvider/ShopifyProvider.server.js +19 -2
  23. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +17 -0
  24. package/dist/esnext/hooks/index.d.ts +1 -0
  25. package/dist/esnext/hooks/index.js +1 -0
  26. package/dist/esnext/hooks/useLocalization/useLocalization.d.ts +3 -2
  27. package/dist/esnext/hooks/useLocalization/useLocalization.js +2 -7
  28. package/dist/esnext/hooks/useProductOptions/helpers.js +1 -1
  29. package/dist/esnext/index.d.ts +6 -5
  30. package/dist/esnext/index.js +6 -5
  31. package/dist/esnext/utilities/image_size.d.ts +1 -0
  32. package/dist/esnext/utilities/image_size.js +27 -26
  33. package/dist/esnext/utilities/index.d.ts +1 -1
  34. package/dist/esnext/utilities/index.js +1 -1
  35. package/dist/esnext/version.d.ts +1 -1
  36. package/dist/esnext/version.js +1 -1
  37. package/package.json +2 -2
  38. package/CHANGELOG.md +0 -2206
  39. package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.d.ts +0 -9
  40. package/dist/esnext/components/LocalizationProvider/LocalizationContext.client.js +0 -2
@@ -1,9 +1,9 @@
1
1
  export * from './components';
2
2
  export * from './hooks';
3
- export * from './foundation/useServerProps';
4
- export * from './foundation/useShop';
5
- export * from './foundation/ServerPropsProvider';
6
- export * from './foundation/useUrl';
3
+ export { useServerProps } from './foundation/useServerProps';
4
+ export { useShop } from './foundation/useShop';
5
+ export { ServerPropsProvider, ServerPropsContext, type ServerProps, type ServerPropsContextValue, } from './foundation/ServerPropsProvider';
6
+ export { useUrl } from './foundation/useUrl';
7
7
  export { Head } from './foundation/Head';
8
8
  export * from './utilities';
9
9
  export { gql } from './utilities/graphql-tag';
@@ -1,9 +1,9 @@
1
1
  export * from './components';
2
2
  export * from './hooks';
3
- export * from './foundation/useServerProps';
4
- export * from './foundation/useShop';
5
- export * from './foundation/ServerPropsProvider';
6
- export * from './foundation/useUrl';
3
+ export { useServerProps } from './foundation/useServerProps';
4
+ export { useShop } from './foundation/useShop';
5
+ export { ServerPropsProvider, ServerPropsContext, } from './foundation/ServerPropsProvider';
6
+ export { useUrl } from './foundation/useUrl';
7
7
  export { Head } from './foundation/Head';
8
8
  export * from './utilities';
9
9
  export { gql } from './utilities/graphql-tag';
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
2
  import { useCart } from '../CartProvider';
3
- import { useProductOptions } from '../ProductOptionsProvider';
3
+ import { useProductOptions } from '../../hooks/useProductOptions';
4
4
  import { BaseButton } from '../BaseButton';
5
5
  /**
6
6
  * The `AddToCartButton` component renders a button that adds an item to the cart when pressed.
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { getShopifyImageDimensions, shopifyImageLoader, addImageSizeParametersToUrl, } from '../../utilities';
2
+ import { getShopifyImageDimensions, shopifyImageLoader, addImageSizeParametersToUrl, IMG_SRC_SET_SIZES, } from '../../utilities';
3
3
  /**
4
4
  * The `Image` component renders an image for the Storefront API's
5
5
  * [Image object](https://shopify.dev/api/storefront/reference/common-objects/image) by using the `data` prop, or a custom location by using the `src` prop. You can [customize this component](https://shopify.dev/api/hydrogen/components#customizing-hydrogen-components) using passthrough props.
@@ -34,7 +34,7 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
34
34
  if (__HYDROGEN_DEV__ && !data.altText && !rest.alt) {
35
35
  console.warn(`<Image/>: the 'data' prop should have the 'altText' property, or the 'alt' prop, and one of them should not be empty. ${`Image: ${data.id ?? data.url}`}`);
36
36
  }
37
- const { width: finalWidth, height: finalHeight } = getShopifyImageDimensions({
37
+ const { width: imgElementWidth, height: imgElementHeight } = getShopifyImageDimensions({
38
38
  data,
39
39
  loaderOptions,
40
40
  elementProps: {
@@ -42,7 +42,7 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
42
42
  height,
43
43
  },
44
44
  });
45
- if (__HYDROGEN_DEV__ && (!finalWidth || !finalHeight)) {
45
+ if (__HYDROGEN_DEV__ && (!imgElementWidth || !imgElementHeight)) {
46
46
  console.warn(`<Image/>: the 'data' prop requires either 'width' or 'data.width', and 'height' or 'data.height' properties. ${`Image: ${data.id ?? data.url}`}`);
47
47
  }
48
48
  let finalSrc = data.url;
@@ -50,8 +50,8 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
50
50
  finalSrc = loader({
51
51
  ...loaderOptions,
52
52
  src: data.url,
53
- width: finalWidth,
54
- height: finalHeight,
53
+ width: imgElementWidth,
54
+ height: imgElementHeight,
55
55
  });
56
56
  if (typeof finalSrc !== 'string' || !finalSrc) {
57
57
  throw new Error(`<Image/>: 'loader' did not return a valid string. ${`Image: ${data.id ?? data.url}`}`);
@@ -59,17 +59,20 @@ function ShopifyImage({ data, width, height, loading, loader = shopifyImageLoade
59
59
  }
60
60
  // determining what the intended width of the image is. For example, if the width is specified and lower than the image width, then that is the maximum image width
61
61
  // to prevent generating a srcset with widths bigger than needed or to generate images that would distort because of being larger than original
62
- const maxWidth = width && finalWidth && width < finalWidth ? width : finalWidth;
62
+ const maxWidth = width && imgElementWidth && width < imgElementWidth
63
+ ? width
64
+ : imgElementWidth;
63
65
  const finalSrcset = rest.srcSet ??
64
66
  internalImageSrcSet({
65
67
  ...loaderOptions,
66
68
  widths,
67
69
  src: data.url,
68
70
  width: maxWidth,
71
+ height: imgElementHeight,
69
72
  loader,
70
73
  });
71
74
  /* eslint-disable hydrogen/prefer-image-component */
72
- return (React.createElement("img", { id: data.id ?? '', alt: data.altText ?? rest.alt ?? '', loading: loading ?? 'lazy', ...rest, src: finalSrc, width: finalWidth ?? undefined, height: finalHeight ?? undefined, srcSet: finalSrcset }));
75
+ return (React.createElement("img", { id: data.id ?? '', alt: data.altText ?? rest.alt ?? '', loading: loading ?? 'lazy', ...rest, src: finalSrc, width: imgElementWidth ?? undefined, height: imgElementHeight ?? undefined, srcSet: finalSrcset }));
73
76
  /* eslint-enable hydrogen/prefer-image-component */
74
77
  }
75
78
  function ExternalImage({ src, width, height, alt, loader, loaderOptions, widths, loading, ...rest }) {
@@ -112,23 +115,29 @@ function ExternalImage({ src, width, height, alt, loader, loaderOptions, widths,
112
115
  height: loaderOptions?.height ?? height, alt: alt ?? '', loading: loading ?? 'lazy', srcSet: finalSrcset }));
113
116
  /* eslint-enable hydrogen/prefer-image-component */
114
117
  }
115
- // based on the default width sizes used by the Shopify liquid HTML tag img_tag plus a 2560 width to account for 2k resolutions
116
- // reference: https://shopify.dev/api/liquid/filters/html-filters#image_tag
117
- const IMG_SRC_SET_SIZES = [352, 832, 1200, 1920, 2560];
118
- function internalImageSrcSet({ src, width, crop, scale, widths, loader, }) {
118
+ function internalImageSrcSet({ src, width, crop, scale, widths, loader, height, }) {
119
119
  const hasCustomWidths = widths && Array.isArray(widths);
120
- if (hasCustomWidths && widths.some((size) => isNaN(size)))
120
+ if (hasCustomWidths && widths.some((size) => isNaN(size))) {
121
121
  throw new Error(`<Image/>: the 'widths' must be an array of numbers`);
122
+ }
123
+ let aspectRatio = 1;
124
+ if (width && height) {
125
+ aspectRatio = Number(height) / Number(width);
126
+ }
122
127
  let setSizes = hasCustomWidths ? widths : IMG_SRC_SET_SIZES;
123
128
  if (!hasCustomWidths &&
124
129
  width &&
125
- width < IMG_SRC_SET_SIZES[IMG_SRC_SET_SIZES.length - 1])
130
+ width < IMG_SRC_SET_SIZES[IMG_SRC_SET_SIZES.length - 1]) {
126
131
  setSizes = IMG_SRC_SET_SIZES.filter((size) => size <= width);
132
+ }
127
133
  const srcGenerator = loader ? loader : addImageSizeParametersToUrl;
128
134
  return setSizes
129
135
  .map((size) => `${srcGenerator({
130
136
  src,
131
137
  width: size,
138
+ // height is not applied if there is no crop
139
+ // if there is crop, then height is applied as a ratio of the original width + height aspect ratio * size
140
+ height: crop ? Number(size) * aspectRatio : undefined,
132
141
  crop,
133
142
  scale,
134
143
  })} ${size}w`)
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { LocalizationContextValue } from './LocalizationContext.client';
2
+ import type { LocalizationContextValue } from '../../foundation/ShopifyProvider/types';
3
3
  export default function LocalizationClientProvider({ localization, children, }: {
4
4
  children: ReactNode;
5
5
  localization: LocalizationContextValue;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { LocalizationContext, } from './LocalizationContext.client';
2
+ import { LocalizationContext } from '../../foundation/ShopifyProvider/ShopifyProvider.client';
3
3
  export default function LocalizationClientProvider({ localization, children, }) {
4
4
  return (React.createElement(LocalizationContext.Provider, { value: localization }, children));
5
5
  }
@@ -1,15 +1,16 @@
1
1
  import { ReactNode } from 'react';
2
+ import { CountryCode, LanguageCode } from '../../storefront-api-types';
2
3
  export interface LocalizationProviderProps {
3
4
  /** A `ReactNode` element. */
4
5
  children: ReactNode;
5
6
  /**
6
7
  * Override the `isoCode` to define the active country
7
8
  */
8
- countryCode?: string;
9
+ countryCode?: CountryCode;
9
10
  /**
10
11
  * Override the `languageCode` to define the active language
11
12
  */
12
- languageCode?: string;
13
+ languageCode?: LanguageCode;
13
14
  }
14
15
  /**
15
16
  * The `LocalizationProvider` component automatically queries the Storefront API's
@@ -2,6 +2,8 @@ import React from 'react';
2
2
  import LocalizationClientProvider from './LocalizationClientProvider.client';
3
3
  import { useShop } from '../../foundation/useShop';
4
4
  import { useServerRequest } from '../../foundation/ServerRequestProvider';
5
+ import { log } from '../../utilities/log';
6
+ import { getLocalizationContextValue } from '../../foundation/ShopifyProvider/ShopifyProvider.server';
5
7
  /**
6
8
  * The `LocalizationProvider` component automatically queries the Storefront API's
7
9
  * [`localization`](https://shopify.dev/api/storefront/reference/common-objects/queryroot) field
@@ -10,18 +12,12 @@ import { useServerRequest } from '../../foundation/ServerRequestProvider';
10
12
  * Any descendents of this provider can use the `useLocalization` hook.
11
13
  */
12
14
  export function LocalizationProvider(props) {
15
+ if (import.meta.env.DEV) {
16
+ log.warn('<LocalizationProvider> is no longer necessary. Pass localization props directly to `<ShopifyProvider>` instead.');
17
+ }
13
18
  const { defaultLanguageCode, defaultCountryCode } = useShop();
14
- const languageCode = (props.languageCode ?? defaultLanguageCode).toUpperCase();
15
- const countryCode = (props.countryCode ?? defaultCountryCode).toUpperCase();
16
19
  const request = useServerRequest();
17
- const localization = {
18
- country: {
19
- isoCode: countryCode,
20
- },
21
- language: {
22
- isoCode: languageCode,
23
- },
24
- };
20
+ const localization = getLocalizationContextValue(defaultLanguageCode, defaultCountryCode, props.languageCode, props.countryCode);
25
21
  request.ctx.localization = localization;
26
22
  return (React.createElement(LocalizationClientProvider, { localization: localization }, props.children));
27
23
  }
@@ -1,2 +1,2 @@
1
- import { ProductOptionsHookValue } from '../../hooks';
1
+ import type { ProductOptionsHookValue } from '../../hooks';
2
2
  export declare const ProductOptionsContext: import("react").Context<ProductOptionsHookValue | null>;
@@ -1,2 +1 @@
1
1
  export { ProductOptionsProvider } from './ProductOptionsProvider.client';
2
- export { useProductOptions } from '../../hooks/useProductOptions/useProductOptions.client';
@@ -1,2 +1 @@
1
1
  export { ProductOptionsProvider } from './ProductOptionsProvider.client';
2
- export { useProductOptions } from '../../hooks/useProductOptions/useProductOptions.client';
@@ -20,9 +20,8 @@ export { CartShopPayButton } from './CartShopPayButton';
20
20
  export { CartCost } from './CartCost';
21
21
  export { CartProvider, useCart, useInstantCheckout } from './CartProvider';
22
22
  export type { State, Status, Cart, CartWithActions, CartAction, } from './CartProvider';
23
- export { ProductOptionsProvider, useProductOptions, } from './ProductOptionsProvider';
23
+ export { ProductOptionsProvider } from './ProductOptionsProvider';
24
24
  export { ProductPrice } from './ProductPrice';
25
25
  export { BuyNowButton } from './BuyNowButton';
26
26
  export { ShopPayButton } from './ShopPayButton';
27
- export { useLocalization } from '../hooks/useLocalization/useLocalization';
28
27
  export { Seo } from './Seo';
@@ -18,9 +18,8 @@ export { CartCheckoutButton } from './CartCheckoutButton';
18
18
  export { CartShopPayButton } from './CartShopPayButton';
19
19
  export { CartCost } from './CartCost';
20
20
  export { CartProvider, useCart, useInstantCheckout } from './CartProvider';
21
- export { ProductOptionsProvider, useProductOptions, } from './ProductOptionsProvider';
21
+ export { ProductOptionsProvider } from './ProductOptionsProvider';
22
22
  export { ProductPrice } from './ProductPrice';
23
23
  export { BuyNowButton } from './BuyNowButton';
24
24
  export { ShopPayButton } from './ShopPayButton';
25
- export { useLocalization } from '../hooks/useLocalization/useLocalization';
26
25
  export { Seo } from './Seo';
@@ -52,7 +52,7 @@ function updateCookie(cookieName, value, maxage, cookieDomain) {
52
52
  const cookieString = stringify(cookieName, value, {
53
53
  maxage,
54
54
  domain: getCookieDomain(cookieDomain),
55
- secure: process.env.NODE_ENV === 'production',
55
+ secure: import.meta.env.PROD,
56
56
  samesite: 'Lax',
57
57
  path: '/',
58
58
  });
@@ -1,10 +1,9 @@
1
- import type { ShopifyContextValue } from '../ShopifyProvider/types';
1
+ import type { ShopifyContextValue, LocalizationContextValue } from '../ShopifyProvider/types';
2
2
  import type { QueryCacheControlHeaders } from '../../utilities/log/log-cache-header';
3
3
  import type { QueryTiming } from '../../utilities/log/log-query-timeline';
4
4
  import type { ResolvedHydrogenConfig, PreloadOptions, QueryKey, RuntimeContext } from '../../types';
5
5
  import { HelmetData as HeadData } from 'react-helmet-async';
6
6
  import { SessionSyncApi } from '../session/session';
7
- import { LocalizationContextValue } from '../../components/LocalizationProvider/LocalizationContext.client';
8
7
  export declare type PreloadQueryEntry = {
9
8
  key: QueryKey;
10
9
  fetcher: (request: HydrogenRequest) => Promise<unknown>;
@@ -130,7 +130,7 @@ export class HydrogenRequest extends Request {
130
130
  }
131
131
  async formData() {
132
132
  // @ts-ignore
133
- if (!__HYDROGEN_WORKER__ && super.formData)
133
+ if (__HYDROGEN_WORKER__ || super.formData)
134
134
  return super.formData();
135
135
  const contentType = this.headers.get('Content-Type') || '';
136
136
  // If mimeType’s essence is "multipart/form-data", then:
@@ -1,2 +1 @@
1
- export { ServerPropsProvider, ServerPropsContext } from './ServerPropsProvider';
2
- export type { ServerProps, ServerPropsContextValue } from './ServerPropsProvider';
1
+ export { ServerPropsProvider, ServerPropsContext, type ServerProps, type ServerPropsContextValue, } from './ServerPropsProvider';
@@ -1 +1 @@
1
- export { ServerPropsProvider, ServerPropsContext } from './ServerPropsProvider';
1
+ export { ServerPropsProvider, ServerPropsContext, } from './ServerPropsProvider';
@@ -1,7 +1,9 @@
1
- import type { ShopifyContextValue } from './types';
1
+ import type { ShopifyContextValue, LocalizationContextValue } from './types';
2
2
  import React, { ReactNode } from 'react';
3
3
  export declare const ShopifyContext: React.Context<ShopifyContextValue | null>;
4
- export declare function ShopifyProviderClient({ children, shopifyConfig, }: {
4
+ export declare const LocalizationContext: React.Context<LocalizationContextValue | null>;
5
+ export declare function ShopifyProviderClient({ children, shopifyConfig, localization, }: {
5
6
  children: ReactNode;
6
7
  shopifyConfig: ShopifyContextValue;
8
+ localization: LocalizationContextValue;
7
9
  }): JSX.Element;
@@ -1,8 +1,10 @@
1
1
  import React, { createContext } from 'react';
2
2
  export const ShopifyContext = createContext(null);
3
- export function ShopifyProviderClient({ children, shopifyConfig, }) {
3
+ export const LocalizationContext = createContext(null);
4
+ export function ShopifyProviderClient({ children, shopifyConfig, localization, }) {
4
5
  if (!shopifyConfig) {
5
6
  throw new Error('The `shopifyConfig` prop should be passed to `ShopifyProvider`');
6
7
  }
7
- return (React.createElement(ShopifyContext.Provider, { value: shopifyConfig }, children));
8
+ return (React.createElement(ShopifyContext.Provider, { value: shopifyConfig },
9
+ React.createElement(LocalizationContext.Provider, { value: localization }, children)));
8
10
  }
@@ -1,4 +1,5 @@
1
- import type { ShopifyProviderProps } from './types';
1
+ import type { ShopifyProviderProps, LocalizationContextValue } from './types';
2
+ import type { CountryCode, LanguageCode } from '../../storefront-api-types';
2
3
  export declare const SHOPIFY_PROVIDER_CONTEXT_KEY: unique symbol;
3
4
  /**
4
5
  * The `ShopifyProvider` component wraps your entire app and provides support for hooks.
@@ -11,6 +12,7 @@ export declare function ShopifyProvider({
11
12
  * Shopify connection information. Defaults to
12
13
  * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
13
14
  */
14
- shopifyConfig,
15
+ shopifyConfig, countryCode, languageCode,
15
16
  /** Any `ReactNode` elements. */
16
17
  children, }: ShopifyProviderProps): JSX.Element;
18
+ export declare function getLocalizationContextValue(defaultLanguageCode: `${LanguageCode}`, defaultCountryCode: `${CountryCode}`, languageCode?: `${LanguageCode}`, countryCode?: `${CountryCode}`): LocalizationContextValue;
@@ -30,7 +30,7 @@ export function ShopifyProvider({
30
30
  * Shopify connection information. Defaults to
31
31
  * [the `shopify` property in the `hydrogen.config.js` file](https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config).
32
32
  */
33
- shopifyConfig,
33
+ shopifyConfig, countryCode, languageCode,
34
34
  /** Any `ReactNode` elements. */
35
35
  children, }) {
36
36
  const request = useServerRequest();
@@ -55,6 +55,23 @@ children, }) {
55
55
  actualShopifyConfig = shopifyConfig;
56
56
  }
57
57
  const shopifyProviderValue = useMemo(() => makeShopifyContext(actualShopifyConfig), [actualShopifyConfig]);
58
+ const localization = getLocalizationContextValue(shopifyProviderValue.defaultLanguageCode, shopifyProviderValue.defaultCountryCode, languageCode, countryCode);
59
+ request.ctx.localization = localization;
58
60
  request.ctx.shopifyConfig = shopifyProviderValue;
59
- return (React.createElement(ShopifyProviderClient, { shopifyConfig: shopifyProviderValue }, children));
61
+ return (React.createElement(ShopifyProviderClient, { shopifyConfig: shopifyProviderValue, localization: localization }, children));
62
+ }
63
+ export function getLocalizationContextValue(defaultLanguageCode, defaultCountryCode, languageCode, countryCode) {
64
+ return useMemo(() => {
65
+ const runtimeLanguageCode = (languageCode ?? defaultLanguageCode).toUpperCase();
66
+ const runtimeCountryCode = (countryCode ?? defaultCountryCode).toUpperCase();
67
+ return {
68
+ country: {
69
+ isoCode: runtimeCountryCode,
70
+ },
71
+ language: {
72
+ isoCode: runtimeLanguageCode,
73
+ },
74
+ locale: `${runtimeLanguageCode}-${runtimeCountryCode}`,
75
+ };
76
+ }, [defaultLanguageCode, defaultCountryCode, countryCode, languageCode]);
60
77
  }
@@ -6,9 +6,26 @@ export interface ShopifyContextValue extends Omit<ShopifyConfig, 'defaultLanguag
6
6
  defaultCountryCode: `${CountryCode}`;
7
7
  storefrontId: string | null;
8
8
  }
9
+ export interface LocalizationContextValue {
10
+ country: {
11
+ isoCode: `${CountryCode}`;
12
+ };
13
+ language: {
14
+ isoCode: `${LanguageCode}`;
15
+ };
16
+ locale: `${LanguageCode}-${CountryCode}`;
17
+ }
9
18
  export declare type ShopifyProviderProps = {
10
19
  /** Shopify connection information. Defaults to the `shopify` property in the `hydrogen.config.js` file. */
11
20
  shopifyConfig?: ShopifyConfig | ShopifyConfigFetcher;
12
21
  /** Any `ReactNode` elements. */
13
22
  children?: ReactNode;
23
+ /**
24
+ * Override the `isoCode` to define the active country
25
+ */
26
+ countryCode?: `${CountryCode}`;
27
+ /**
28
+ * Override the `languageCode` to define the active language
29
+ */
30
+ languageCode?: `${LanguageCode}`;
14
31
  };
@@ -3,3 +3,4 @@ export * from './useProductOptions/types';
3
3
  export { useMoney } from './useMoney';
4
4
  export { useMeasurement } from './useMeasurement';
5
5
  export { useLoadScript } from './useLoadScript';
6
+ export { useLocalization } from './useLocalization/useLocalization';
@@ -3,3 +3,4 @@ export * from './useProductOptions/types';
3
3
  export { useMoney } from './useMoney';
4
4
  export { useMeasurement } from './useMeasurement';
5
5
  export { useLoadScript } from './useLoadScript';
6
+ export { useLocalization } from './useLocalization/useLocalization';
@@ -1,4 +1,5 @@
1
- import { type LocalizationContextValue } from '../../components/LocalizationProvider/LocalizationContext.client';
1
+ import type { LocalizationContextValue } from '../../foundation/ShopifyProvider/types';
2
+ import { CountryCode, LanguageCode } from '../../storefront-api-types';
2
3
  export declare function useLocalization(): LocalizationContextValue & {
3
- locale: string;
4
+ locale: `${LanguageCode}-${CountryCode}`;
4
5
  };
@@ -1,14 +1,9 @@
1
- import { useMemo } from 'react';
2
- import { LocalizationContext, } from '../../components/LocalizationProvider/LocalizationContext.client';
1
+ import { LocalizationContext } from '../../foundation/ShopifyProvider/ShopifyProvider.client';
3
2
  import { useEnvContext } from '../../foundation/ssr-interop';
4
3
  export function useLocalization() {
5
4
  const localization = useEnvContext((req) => req.ctx.localization, LocalizationContext);
6
5
  if (localization == null) {
7
6
  throw new Error('No Localization Context available');
8
7
  }
9
- const localizationValue = useMemo(() => ({
10
- ...localization,
11
- locale: localization.language.isoCode + '-' + localization.country.isoCode,
12
- }), [localization]);
13
- return localizationValue;
8
+ return localization;
14
9
  }
@@ -19,7 +19,7 @@ export function getSelectedVariant(variants, choices) {
19
19
  export function getOptions(variants) {
20
20
  const map = variants.reduce((memo, variant) => {
21
21
  if (!variant.selectedOptions) {
22
- throw new Error(`getOptions requires 'variant.selectedOptions`);
22
+ throw new Error(`'getOptions' requires 'variant.selectedOptions'`);
23
23
  }
24
24
  variant?.selectedOptions?.forEach((opt) => {
25
25
  memo[opt?.name ?? ''] = memo[opt?.name ?? ''] || new Set();
@@ -9,10 +9,12 @@ export * from './client';
9
9
  * The following are exported from this file because they are intended to be available
10
10
  * *only* on the server.
11
11
  */
12
- export * from './foundation/';
13
- export * from './hooks/useShopQuery/hooks';
14
- export * from './foundation/useQuery/hooks';
15
- export * from './foundation/useServerProps';
12
+ export { ServerPropsProvider, ServerPropsContext, type ServerProps, type ServerPropsContextValue, } from './foundation/ServerPropsProvider';
13
+ export { useShop } from './foundation/useShop';
14
+ export { useUrl } from './foundation/useUrl';
15
+ export { useShopQuery, type UseShopQueryResponse, } from './hooks/useShopQuery/hooks';
16
+ export { useQuery, type HydrogenUseQueryOptions, } from './foundation/useQuery/hooks';
17
+ export { useServerProps } from './foundation/useServerProps';
16
18
  export { FileRoutes } from './foundation/FileRoutes/FileRoutes.server';
17
19
  export { Route } from './foundation/Route/Route.server';
18
20
  export { Router } from './foundation/Router/Router.server';
@@ -25,7 +27,6 @@ export { useServerAnalytics } from './foundation/Analytics/hook';
25
27
  export { ShopifyAnalytics } from './foundation/Analytics/connectors/Shopify/ShopifyAnalytics.server';
26
28
  export { ShopifyAnalyticsConstants } from './foundation/Analytics/connectors/Shopify/const';
27
29
  export { useSession } from './foundation/useSession/useSession';
28
- export { useLocalization } from './hooks/useLocalization/useLocalization';
29
30
  export { Cookie } from './foundation/Cookie/Cookie';
30
31
  /**
31
32
  * Export server-only CartQuery here instead of `CartProvider.client` to prevent
@@ -9,10 +9,12 @@ export * from './client';
9
9
  * The following are exported from this file because they are intended to be available
10
10
  * *only* on the server.
11
11
  */
12
- export * from './foundation/';
13
- export * from './hooks/useShopQuery/hooks';
14
- export * from './foundation/useQuery/hooks';
15
- export * from './foundation/useServerProps';
12
+ export { ServerPropsProvider, ServerPropsContext, } from './foundation/ServerPropsProvider';
13
+ export { useShop } from './foundation/useShop';
14
+ export { useUrl } from './foundation/useUrl';
15
+ export { useShopQuery, } from './hooks/useShopQuery/hooks';
16
+ export { useQuery, } from './foundation/useQuery/hooks';
17
+ export { useServerProps } from './foundation/useServerProps';
16
18
  export { FileRoutes } from './foundation/FileRoutes/FileRoutes.server';
17
19
  export { Route } from './foundation/Route/Route.server';
18
20
  export { Router } from './foundation/Router/Router.server';
@@ -25,7 +27,6 @@ export { useServerAnalytics } from './foundation/Analytics/hook';
25
27
  export { ShopifyAnalytics } from './foundation/Analytics/connectors/Shopify/ShopifyAnalytics.server';
26
28
  export { ShopifyAnalyticsConstants } from './foundation/Analytics/connectors/Shopify/const';
27
29
  export { useSession } from './foundation/useSession/useSession';
28
- export { useLocalization } from './hooks/useLocalization/useLocalization';
29
30
  export { Cookie } from './foundation/Cookie/Cookie';
30
31
  /**
31
32
  * Export server-only CartQuery here instead of `CartProvider.client` to prevent
@@ -1,6 +1,7 @@
1
1
  import type { Image as ImageType } from '../storefront-api-types';
2
2
  import type { PartialDeep } from 'type-fest';
3
3
  import type { ShopifyLoaderOptions, ShopifyLoaderParams } from '../components/Image';
4
+ export declare const IMG_SRC_SET_SIZES: number[];
4
5
  /**
5
6
  * Adds image size parameters to an image URL hosted by Shopify's CDN
6
7
  */
@@ -7,26 +7,31 @@ const PRODUCTION_CDN_HOSTNAMES = [
7
7
  ];
8
8
  const LOCAL_CDN_HOSTNAMES = ['spin.dev'];
9
9
  const ALL_CDN_HOSTNAMES = [...PRODUCTION_CDN_HOSTNAMES, ...LOCAL_CDN_HOSTNAMES];
10
+ // based on the default width sizes used by the Shopify liquid HTML tag img_tag plus a 2560 width to account for 2k resolutions
11
+ // reference: https://shopify.dev/api/liquid/filters/html-filters#image_tag
12
+ export const IMG_SRC_SET_SIZES = [352, 832, 1200, 1920, 2560];
10
13
  /**
11
14
  * Adds image size parameters to an image URL hosted by Shopify's CDN
12
15
  */
13
16
  export function addImageSizeParametersToUrl({ src, width, height, crop, scale, }) {
14
- if (scale) {
15
- // Have to do this specifically for 'scale' because it doesn't currently work otherwise.
16
- // I'm also intentionally leaving 'scale' as a searchParam because that way it'll "just work" in the future and we can just delete this whole section of code
17
- // We assume here that the last `.` is the delimiter between the file name and the file type
18
- const baseUrl = new URL(src);
19
- const fileDelimiterIndex = baseUrl.pathname.lastIndexOf('.');
20
- const fileName = baseUrl.pathname.slice(0, fileDelimiterIndex);
21
- const fileType = baseUrl.pathname.slice(fileDelimiterIndex);
22
- baseUrl.pathname = `${fileName}${`@${scale.toString()}x`}${fileType}`;
23
- src = baseUrl.toString();
24
- }
25
17
  const newUrl = new URL(src);
26
- width && newUrl.searchParams.append('width', width.toString());
27
- height && newUrl.searchParams.append('height', height.toString());
18
+ const multipliedScale = scale ?? 1;
19
+ if (width) {
20
+ let finalWidth;
21
+ if (typeof width === 'string') {
22
+ finalWidth = (IMG_SRC_SET_SIZES[0] * multipliedScale).toString();
23
+ }
24
+ else {
25
+ finalWidth = (Number(width) * multipliedScale).toString();
26
+ }
27
+ newUrl.searchParams.append('width', finalWidth);
28
+ }
29
+ if (height && typeof height === 'number') {
30
+ newUrl.searchParams.append('height', (height * multipliedScale).toString());
31
+ }
28
32
  crop && newUrl.searchParams.append('crop', crop);
29
- scale && newUrl.searchParams.append('scale', scale.toString());
33
+ // for now we intentionally leave off the scale param, and instead multiple width & height by scale instead
34
+ // scale && newUrl.searchParams.append('scale', scale.toString());
30
35
  return newUrl.toString();
31
36
  }
32
37
  export function shopifyImageLoader(params) {
@@ -57,14 +62,12 @@ export function getShopifyImageDimensions({ data: sfapiImage, loaderOptions, ele
57
62
  if (loaderOptions?.width || loaderOptions?.height) {
58
63
  return {
59
64
  width: loaderOptions?.width ??
60
- (aspectRatio
61
- ? // @ts-expect-error if width isn't defined, then height has to be defined due to the If statement above
62
- Math.round(aspectRatio * loaderOptions.height)
65
+ (aspectRatio && typeof loaderOptions.height === 'number'
66
+ ? Math.round(aspectRatio * loaderOptions.height)
63
67
  : null),
64
68
  height: loaderOptions?.height ??
65
- (aspectRatio
66
- ? // @ts-expect-error if height isn't defined, then width has to be defined due to the If statement above
67
- Math.round(aspectRatio * loaderOptions.width)
69
+ (aspectRatio && typeof loaderOptions.width === 'number'
70
+ ? Math.round(aspectRatio * loaderOptions.width)
68
71
  : null),
69
72
  };
70
73
  }
@@ -72,14 +75,12 @@ export function getShopifyImageDimensions({ data: sfapiImage, loaderOptions, ele
72
75
  if (elementProps?.width || elementProps?.height) {
73
76
  return {
74
77
  width: elementProps?.width ??
75
- (aspectRatio
76
- ? // @ts-expect-error if width isn't defined, then height has to be defined due to the If statement above
77
- Math.round(aspectRatio * elementProps.height)
78
+ (aspectRatio && typeof elementProps.height === 'number'
79
+ ? Math.round(aspectRatio * elementProps.height)
78
80
  : null),
79
81
  height: elementProps?.height ??
80
- (aspectRatio
81
- ? // @ts-expect-error if height isn't defined, then width has to be defined due to the If statement above
82
- Math.round(aspectRatio * elementProps.width)
82
+ (aspectRatio && typeof elementProps.width === 'number'
83
+ ? Math.round(aspectRatio * elementProps.width)
83
84
  : null),
84
85
  };
85
86
  }
@@ -1,4 +1,4 @@
1
- export { addImageSizeParametersToUrl, getShopifyImageDimensions, shopifyImageLoader, } from './image_size';
1
+ export { addImageSizeParametersToUrl, getShopifyImageDimensions, shopifyImageLoader, IMG_SRC_SET_SIZES, } from './image_size';
2
2
  export { YouTube, Vimeo, addParametersToEmbeddedVideoUrl, useEmbeddedVideoUrl, } from './video_parameters';
3
3
  export { loadScript } from './load_script';
4
4
  export { wrapPromise } from './suspense';
@@ -1,4 +1,4 @@
1
- export { addImageSizeParametersToUrl, getShopifyImageDimensions, shopifyImageLoader, } from './image_size';
1
+ export { addImageSizeParametersToUrl, getShopifyImageDimensions, shopifyImageLoader, IMG_SRC_SET_SIZES, } from './image_size';
2
2
  export { addParametersToEmbeddedVideoUrl, useEmbeddedVideoUrl, } from './video_parameters';
3
3
  export { loadScript } from './load_script';
4
4
  export { wrapPromise } from './suspense';
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "1.0.1";
1
+ export declare const LIB_VERSION = "1.0.2";
@@ -1 +1 @@
1
- export const LIB_VERSION = '1.0.1';
1
+ export const LIB_VERSION = '1.0.2';