UNPKG

@shopify/hydrogen-react

Version:

React components, hooks, and utilities for creating custom Shopify storefronts

352 lines (351 loc) • 10 kB
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