@shopify/hydrogen-react
Version:
React components, hooks, and utilities for creating custom Shopify storefronts
352 lines (351 loc) • 10 kB
JavaScript
import { jsx } from "react/jsx-runtime";
import * as React from "react";
const IMAGE_FRAGMENT = `#graphql
fragment Image on Image {
altText
url
width
height
}
`;
const Image = React.forwardRef(
({
alt,
aspectRatio,
crop = "center",
data,
decoding = "async",
height = "auto",
loader = shopifyLoader,
loading = "lazy",
sizes,
src,
srcSetOptions = {
intervals: 15,
startingWidth: 200,
incrementSize: 200,
placeholderWidth: 100
},
width = "100%",
...passthroughProps
}, ref) => {
const normalizedData = React.useMemo(() => {
const dataWidth = (data == null ? void 0 : data.width) && (data == null ? void 0 : data.height) ? data == null ? void 0 : data.width : void 0;
const dataHeight = (data == null ? void 0 : data.width) && (data == null ? void 0 : data.height) ? data == null ? void 0 : data.height : void 0;
return {
width: dataWidth,
height: dataHeight,
unitsMatch: Boolean(unitsMatch(dataWidth, dataHeight))
};
}, [data]);
const normalizedProps = React.useMemo(() => {
const nWidthProp = width || "100%";
const widthParts = getUnitValueParts(nWidthProp.toString());
const nWidth = `${widthParts.number}${widthParts.unit}`;
const autoHeight = height === void 0 || height === null;
const heightParts = autoHeight ? null : getUnitValueParts(height.toString());
const fixedHeight = heightParts ? `${heightParts.number}${heightParts.unit}` : "";
const nHeight = autoHeight ? "auto" : fixedHeight;
const nSrc = src || (data == null ? void 0 : data.url);
const nAlt = (data == null ? void 0 : data.altText) && !alt ? data == null ? void 0 : data.altText : alt || "";
const nAspectRatio = aspectRatio ? aspectRatio : normalizedData.unitsMatch ? [
getNormalizedFixedUnit(normalizedData.width),
getNormalizedFixedUnit(normalizedData.height)
].join("/") : void 0;
return {
width: nWidth,
height: nHeight,
src: nSrc,
alt: nAlt,
aspectRatio: nAspectRatio
};
}, [
width,
height,
src,
data,
alt,
aspectRatio,
normalizedData,
passthroughProps == null ? void 0 : passthroughProps.key
]);
const { intervals, startingWidth, incrementSize, placeholderWidth } = srcSetOptions;
const imageWidths = React.useMemo(() => {
return generateImageWidths(
width,
intervals,
startingWidth,
incrementSize
);
}, [width, intervals, startingWidth, incrementSize]);
const fixedWidth = isFixedWidth(normalizedProps.width);
if (fixedWidth) {
return /* @__PURE__ */ jsx(
FixedWidthImage,
{
aspectRatio,
crop,
decoding,
height,
imageWidths,
loader,
loading,
normalizedProps,
passthroughProps,
ref,
width,
data
}
);
} else {
return /* @__PURE__ */ jsx(
FluidImage,
{
aspectRatio,
crop,
decoding,
imageWidths,
loader,
loading,
normalizedProps,
passthroughProps,
placeholderWidth,
ref,
sizes,
data
}
);
}
}
);
const FixedWidthImage = React.forwardRef(
({
aspectRatio,
crop,
decoding,
height,
imageWidths,
loader = shopifyLoader,
loading,
normalizedProps,
passthroughProps,
width,
data
}, ref) => {
const fixed = React.useMemo(() => {
const intWidth = getNormalizedFixedUnit(width);
const intHeight = getNormalizedFixedUnit(height);
const fixedAspectRatio = aspectRatio ? aspectRatio : unitsMatch(normalizedProps.width, normalizedProps.height) ? [intWidth, intHeight].join("/") : normalizedProps.aspectRatio ? normalizedProps.aspectRatio : void 0;
const sizesArray = imageWidths === void 0 ? void 0 : generateSizes(imageWidths, fixedAspectRatio, crop, {
width: (data == null ? void 0 : data.width) ?? void 0,
height: (data == null ? void 0 : data.height) ?? void 0
});
const fixedHeight = intHeight ? intHeight : fixedAspectRatio && intWidth ? intWidth * (parseAspectRatio(fixedAspectRatio) ?? 1) : void 0;
const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);
const src = loader({
src: normalizedProps.src,
width: intWidth,
height: fixedHeight,
crop: normalizedProps.height === "auto" ? void 0 : crop
});
return {
width: intWidth,
aspectRatio: fixedAspectRatio,
height: fixedHeight,
srcSet,
src
};
}, [
aspectRatio,
crop,
data,
height,
imageWidths,
loader,
normalizedProps,
width
]);
return /* @__PURE__ */ jsx(
"img",
{
ref,
alt: normalizedProps.alt,
decoding,
height: fixed.height,
loading,
src: fixed.src,
srcSet: fixed.srcSet,
width: fixed.width,
style: {
aspectRatio: fixed.aspectRatio,
...passthroughProps.style
},
...passthroughProps
}
);
}
);
const FluidImage = React.forwardRef(
({
crop,
decoding,
imageWidths,
loader = shopifyLoader,
loading,
normalizedProps,
passthroughProps,
placeholderWidth,
sizes,
data
}, ref) => {
const fluid = React.useMemo(() => {
const sizesArray = imageWidths === void 0 ? void 0 : generateSizes(imageWidths, normalizedProps.aspectRatio, crop, {
width: (data == null ? void 0 : data.width) ?? void 0,
height: (data == null ? void 0 : data.height) ?? void 0
});
const placeholderHeight = normalizedProps.aspectRatio && placeholderWidth ? placeholderWidth * (parseAspectRatio(normalizedProps.aspectRatio) ?? 1) : void 0;
const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);
const src = loader({
src: normalizedProps.src,
width: placeholderWidth,
height: placeholderHeight,
crop
});
return {
placeholderHeight,
srcSet,
src
};
}, [crop, data, imageWidths, loader, normalizedProps, placeholderWidth]);
return /* @__PURE__ */ jsx(
"img",
{
ref,
alt: normalizedProps.alt,
decoding,
height: fluid.placeholderHeight,
loading,
sizes,
src: fluid.src,
srcSet: fluid.srcSet,
width: placeholderWidth,
...passthroughProps,
style: {
width: normalizedProps.width,
aspectRatio: normalizedProps.aspectRatio,
...passthroughProps.style
}
}
);
}
);
const PLACEHOLDER_DOMAIN = "https://placeholder.shopify.com";
function shopifyLoader({ src, width, height, crop }) {
if (!src) {
return "";
}
const url = new URL(src, PLACEHOLDER_DOMAIN);
if (width) {
url.searchParams.append("width", Math.round(width).toString());
}
if (height) {
url.searchParams.append("height", Math.round(height).toString());
}
if (crop) {
url.searchParams.append("crop", crop);
}
return url.href.replace(PLACEHOLDER_DOMAIN, "");
}
function unitsMatch(width = "100%", height = "auto") {
return getUnitValueParts(width.toString()).unit === getUnitValueParts(height.toString()).unit;
}
function getUnitValueParts(value) {
const unit = value.replace(/[0-9.]/g, "");
const number = parseFloat(value.replace(unit, ""));
return {
unit: unit === "" ? number === void 0 ? "auto" : "px" : unit,
number
};
}
function getNormalizedFixedUnit(value) {
if (value === void 0) {
return;
}
const { unit, number } = getUnitValueParts(value.toString());
switch (unit) {
case "em":
return number * 16;
case "rem":
return number * 16;
case "px":
return number;
case "":
return number;
default:
return;
}
}
function isFixedWidth(width) {
const fixedEndings = /\d(px|em|rem)$/;
return typeof width === "number" || fixedEndings.test(width);
}
function generateSrcSet(src, sizesArray, loader = shopifyLoader) {
if (!src) {
return "";
}
if ((sizesArray == null ? void 0 : sizesArray.length) === 0 || !sizesArray) {
return src;
}
return sizesArray.map(
(size, i) => `${loader({
src,
width: size.width,
height: size.height,
crop: size.crop
})} ${sizesArray.length === 3 ? `${i + 1}x` : `${size.width ?? 0}w`}`
).join(`, `);
}
function generateImageWidths(width = "100%", intervals, startingWidth, incrementSize) {
const responsive = Array.from(
{ length: intervals },
(_, i) => i * incrementSize + startingWidth
);
const fixed = Array.from(
{ length: 3 },
(_, i) => (i + 1) * (getNormalizedFixedUnit(width) ?? 0)
);
return isFixedWidth(width) ? fixed : responsive;
}
function parseAspectRatio(aspectRatio) {
if (!aspectRatio) return;
const [width, height] = aspectRatio.split("/");
return 1 / (Number(width) / Number(height));
}
function generateSizes(imageWidths, aspectRatio, crop = "center", sourceDimensions) {
if (!imageWidths) return;
return imageWidths.map((width) => {
return {
width,
height: aspectRatio ? width * (parseAspectRatio(aspectRatio) ?? 1) : void 0,
crop
};
}).filter(({ width, height }) => {
if ((sourceDimensions == null ? void 0 : sourceDimensions.width) && width > sourceDimensions.width) {
return false;
}
if ((sourceDimensions == null ? void 0 : sourceDimensions.height) && height && height > sourceDimensions.height) {
return false;
}
return true;
});
}
export {
IMAGE_FRAGMENT,
Image,
generateImageWidths,
generateSizes,
generateSrcSet,
parseAspectRatio,
shopifyLoader
};
//# sourceMappingURL=Image.mjs.map