{"version":3,"file":"image-size.mjs","sources":["../../src/image-size.ts"],"sourcesContent":["import type {Image as ImageType} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\nimport type {ShopifyLoaderOptions, ShopifyLoaderParams} from './Image.js';\n\n// TODO: Are there other CDNs missing from here?\nconst PRODUCTION_CDN_HOSTNAMES = [\n  'cdn.shopify.com',\n  'cdn.shopifycdn.net',\n  'shopify-assets.shopifycdn.com',\n  'shopify-assets.shopifycdn.net',\n];\nconst LOCAL_CDN_HOSTNAMES = ['spin.dev'];\nconst ALL_CDN_HOSTNAMES = [...PRODUCTION_CDN_HOSTNAMES, ...LOCAL_CDN_HOSTNAMES];\n\n// based on the default width sizes used by the Shopify liquid HTML tag img_tag plus a 2560 width to account for 2k resolutions\n// reference: https://shopify.dev/api/liquid/filters/html-filters#image_tag\nexport const IMG_SRC_SET_SIZES = [352, 832, 1200, 1920, 2560];\n\n/**\n * Adds image size parameters to an image URL hosted by Shopify's CDN\n */\nexport function addImageSizeParametersToUrl({\n  src,\n  width,\n  height,\n  crop,\n  scale,\n}: ShopifyLoaderParams) {\n  const newUrl = new URL(src);\n\n  const multipliedScale = scale ?? 1;\n\n  if (width) {\n    let finalWidth: string;\n\n    if (typeof width === 'string') {\n      finalWidth = (IMG_SRC_SET_SIZES[0] * multipliedScale).toString();\n    } else {\n      finalWidth = (Number(width) * multipliedScale).toString();\n    }\n\n    newUrl.searchParams.append('width', finalWidth);\n  }\n\n  if (height && typeof height === 'number') {\n    newUrl.searchParams.append('height', (height * multipliedScale).toString());\n  }\n\n  crop && newUrl.searchParams.append('crop', crop);\n\n  // for now we intentionally leave off the scale param, and instead multiple width & height by scale instead\n  // scale && newUrl.searchParams.append('scale', scale.toString());\n\n  return newUrl.toString();\n}\n\nexport function shopifyImageLoader(params: ShopifyLoaderParams) {\n  const newSrc = new URL(params.src);\n  const isShopifyServedImage = ALL_CDN_HOSTNAMES.some((allowedHostname) =>\n    newSrc.hostname.endsWith(allowedHostname)\n  );\n\n  if (\n    !isShopifyServedImage ||\n    (!params.width && !params.height && !params.crop && !params.scale)\n  ) {\n    return params.src;\n  }\n\n  return addImageSizeParametersToUrl(params);\n}\n\ntype HtmlImageProps = React.ImgHTMLAttributes<HTMLImageElement>;\n\nexport type GetShopifyImageDimensionsProps = {\n  data: Pick<\n    PartialDeep<ImageType, {recurseIntoArrays: true}>,\n    'altText' | 'url' | 'id' | 'width' | 'height'\n  >;\n  loaderOptions?: ShopifyLoaderOptions;\n  elementProps?: {\n    width?: HtmlImageProps['width'];\n    height?: HtmlImageProps['height'];\n  };\n};\n\ntype GetShopifyImageDimensionsPropsReturn = {\n  width: number | string | null;\n  height: number | string | null;\n};\n\n/**\n * Width and height are determined using the followiing priority list:\n * 1. `loaderOptions`'s width/height\n * 2. `elementProps`'s width/height\n * 3. `data`'s width/height\n *\n * If only one of `width` or `height` are defined, then the other will attempt to be calculated based on the Image's aspect ratio,\n * provided that both `data.width` and `data.height` are available. If not, then the aspect ratio cannot be determined and the missing\n * value will reamin as `null`\n */\nexport function getShopifyImageDimensions({\n  data: sfapiImage,\n  loaderOptions,\n  elementProps,\n}: GetShopifyImageDimensionsProps): GetShopifyImageDimensionsPropsReturn {\n  let aspectRatio: number | null = null;\n\n  if (sfapiImage?.width && sfapiImage?.height) {\n    aspectRatio = sfapiImage?.width / sfapiImage?.height;\n  }\n\n  //  * 1. `loaderOptions`'s width/height\n  if (loaderOptions?.width || loaderOptions?.height) {\n    return {\n      width:\n        loaderOptions?.width ??\n        (aspectRatio && typeof loaderOptions.height === 'number'\n          ? Math.round(aspectRatio * loaderOptions.height)\n          : null),\n      height:\n        loaderOptions?.height ??\n        (aspectRatio && typeof loaderOptions.width === 'number'\n          ? Math.round(aspectRatio * loaderOptions.width)\n          : null),\n    };\n  }\n\n  //  * 2. `elementProps`'s width/height\n  if (elementProps?.width || elementProps?.height) {\n    return {\n      width:\n        elementProps?.width ??\n        (aspectRatio && typeof elementProps.height === 'number'\n          ? Math.round(aspectRatio * elementProps.height)\n          : null),\n      height:\n        elementProps?.height ??\n        (aspectRatio && typeof elementProps.width === 'number'\n          ? Math.round(aspectRatio * elementProps.width)\n          : null),\n    };\n  }\n\n  //  * 3. `data`'s width/height\n  if (sfapiImage?.width || sfapiImage?.height) {\n    return {\n      // can't calculate the aspect ratio here\n      width: sfapiImage?.width ?? null,\n      height: sfapiImage?.height ?? null,\n    };\n  }\n\n  return {width: null, height: null};\n}\n"],"names":[],"mappings":"AAKA,MAAM,2BAA2B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,MAAM,sBAAsB,CAAC,UAAU;AACvC,MAAM,oBAAoB,CAAC,GAAG,0BAA0B,GAAG,mBAAmB;AAIvE,MAAM,oBAAoB,CAAC,KAAK,KAAK,MAAM,MAAM,IAAI;AAKrD,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AAChB,QAAA,SAAS,IAAI,IAAI,GAAG;AAE1B,QAAM,kBAAkB,wBAAS;AAEjC,MAAI,OAAO;AACL,QAAA;AAEA,QAAA,OAAO,UAAU,UAAU;AACf,oBAAA,kBAAkB,KAAK,iBAAiB,SAAS;AAAA,IAAA,OAC1D;AACL,oBAAc,OAAO,KAAK,IAAI,iBAAiB,SAAS;AAAA,IAC1D;AAEO,WAAA,aAAa,OAAO,SAAS,UAAU;AAAA,EAChD;AAEI,MAAA,UAAU,OAAO,WAAW,UAAU;AACxC,WAAO,aAAa,OAAO,WAAW,SAAS,iBAAiB,UAAU;AAAA,EAC5E;AAEA,UAAQ,OAAO,aAAa,OAAO,QAAQ,IAAI;AAK/C,SAAO,OAAO;AAChB;AAEO,SAAS,mBAAmB,QAA6B;AAC9D,QAAM,SAAS,IAAI,IAAI,OAAO,GAAG;AACjC,QAAM,uBAAuB,kBAAkB;AAAA,IAAK,CAAC,oBACnD,OAAO,SAAS,SAAS,eAAe;AAAA,EAAA;AAG1C,MACE,CAAC,wBACA,CAAC,OAAO,SAAS,CAAC,OAAO,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,OAC5D;AACA,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO,4BAA4B,MAAM;AAC3C;AA+BO,SAAS,0BAA0B;AAAA,EACxC,MAAM;AAAA,EACN;AAAA,EACA;AACF,GAAyE;AApGzE;AAqGE,MAAI,cAA6B;AAE7B,OAAA,yCAAY,WAAS,yCAAY,SAAQ;AAC7B,mBAAA,yCAAY,UAAQ,yCAAY;AAAA,EAChD;AAGI,OAAA,+CAAe,WAAS,+CAAe,SAAQ;AAC1C,WAAA;AAAA,MACL,QACE,oDAAe,UAAf,YACC,eAAe,OAAO,cAAc,WAAW,WAC5C,KAAK,MAAM,cAAc,cAAc,MAAM,IAC7C;AAAA,MACN,SACE,oDAAe,WAAf,YACC,eAAe,OAAO,cAAc,UAAU,WAC3C,KAAK,MAAM,cAAc,cAAc,KAAK,IAC5C;AAAA,IAAA;AAAA,EAEV;AAGI,OAAA,6CAAc,WAAS,6CAAc,SAAQ;AACxC,WAAA;AAAA,MACL,QACE,kDAAc,UAAd,YACC,eAAe,OAAO,aAAa,WAAW,WAC3C,KAAK,MAAM,cAAc,aAAa,MAAM,IAC5C;AAAA,MACN,SACE,kDAAc,WAAd,YACC,eAAe,OAAO,aAAa,UAAU,WAC1C,KAAK,MAAM,cAAc,aAAa,KAAK,IAC3C;AAAA,IAAA;AAAA,EAEV;AAGI,OAAA,yCAAY,WAAS,yCAAY,SAAQ;AACpC,WAAA;AAAA,MAEL,QAAO,8CAAY,UAAZ,YAAqB;AAAA,MAC5B,SAAQ,8CAAY,WAAZ,YAAsB;AAAA,IAAA;AAAA,EAElC;AAEA,SAAO,EAAC,OAAO,MAAM,QAAQ,KAAI;AACnC;"}