sanity-image 0.1.2 → 0.1.4
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.
- package/README.md +2 -2
- package/dist/SanityImage.d.ts +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/index.js +1 -1
- package/dist/mjs/index.js.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/urlBuilder.d.ts +23 -3
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ size):
|
|
|
53
53
|
import { SanityImage } from "sanity-image"
|
|
54
54
|
|
|
55
55
|
const YourSweetComponent = ({ image }: ComponentProps) => (
|
|
56
|
-
<
|
|
56
|
+
<SanityImage
|
|
57
57
|
// Pass the Sanity Image ID (`_id`) (e.g., `image-abcde12345-1200x800-jpg`)
|
|
58
58
|
id={image._id}
|
|
59
59
|
baseUrl="https://cdn.sanity.io/images/abcd1234/production"
|
|
@@ -68,7 +68,7 @@ const YourSweetComponent = ({ image }: ComponentProps) => (
|
|
|
68
68
|
import { SanityImage } from "sanity-image"
|
|
69
69
|
|
|
70
70
|
const YourSweetComponent = ({ image }: ComponentProps) => (
|
|
71
|
-
<
|
|
71
|
+
<SanityImage
|
|
72
72
|
// Pass the Sanity Image ID (`_id`) (e.g., `image-abcde12345-1200x800-jpg`)
|
|
73
73
|
id={image._id}
|
|
74
74
|
//
|
package/dist/SanityImage.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import React, { type ReactElement } from "react";
|
|
2
2
|
import type { PolymorphicComponentProps, SanityImageProps } from "./types";
|
|
3
|
-
export declare const SanityImage: <C extends React.ElementType
|
|
3
|
+
export declare const SanityImage: <C extends React.ElementType = "img">({ as: component, baseUrl, projectId, dataset, id, hotspot, crop, width, height, mode, preview, htmlWidth, htmlHeight, htmlId, queryParams, ...rest }: PolymorphicComponentProps<C, SanityImageProps>) => ReactElement;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var _=Object.create;var
|
|
1
|
+
"use strict";var _=Object.create;var P=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var J=(r,t)=>{for(var e in t)P(r,e,{get:t[e],enumerable:!0})},R=(r,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of k(t))!G.call(r,n)&&n!==e&&P(r,n,{get:()=>t[n],enumerable:!(a=K(t,n))||a.enumerable});return r};var T=(r,t,e)=>(e=r!=null?_(B(r)):{},R(t||!r||!r.__esModule?P(e,"default",{value:r,enumerable:!0}):e,r)),Y=r=>R(P({},"__esModule",{value:!0}),r);var X={};J(X,{ImageWithPreview:()=>S,SanityImage:()=>j,buildSrc:()=>h,buildSrcSet:()=>x,parseImageId:()=>y});module.exports=Y(X);var F=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,y=r=>{let t=F.exec(r),[,e,a,n]=t!=null?t:[];if(!t||!e||!a||!n)throw new Error(`Could not parse image ID "${r}"`);let[o,m]=a.split("x").map(d=>Number.parseInt(d,10));if(Number.isNaN(o)||Number.isNaN(m)||!o||!m)throw new Error(`Invalid dimensions "${a}"`);return{assetId:e,dimensions:{height:m,width:o,aspectRatio:o/m},format:n}},w=r=>{let t=r.lastIndexOf("-");return r.slice(6,t)+"."+r.slice(t+1)};var h=({baseUrl:r,...t})=>{let{metadata:e,...a}=M({...t,options:{includeMetadata:!0}});if(!e)throw new Error("Missing image output metadata");return{src:`${`${r}${w(t.id)}`}?${O(a)}`,width:e.outputDimensions.width,height:e.outputDimensions.height}},x=({id:r,mode:t="contain",width:e,height:a,hotspot:n,crop:o,baseUrl:m})=>{let{w:d,h:u}=M({id:r,mode:t,width:e,height:a,hotspot:n,crop:o}),I=`${m}${w(r)}`,p=H(d).map(i=>{let s=Math.round(d*i),c=u&&Math.round(u*i);if(i<1&&s<50)return null;let l=M({id:r,mode:t,width:s,height:c,hotspot:n,crop:o});return`${I}?${O(l)} ${l.w}w`}).filter(i=>!!i);return Array.from(new Set(p))},q=({id:r,baseUrl:t})=>{let{assetId:e,dimensions:a,format:n}=y(r);return{src:`${t}${e}-${a.width}x${a.height}.${n}`,width:a.width,height:a.height}},H=r=>r<160?[.5,1,2]:r<750?[.5,1,1.5,2]:r<1400?[.25,.5,.75,1,1.5,2]:[.25,.5,.75,1,1.25,1.5,1.75,2],M=({id:r,mode:t="contain",width:e,height:a,hotspot:n,crop:o,queryParams:m,options:{includeMetadata:d=!1}={}})=>{let u=y(r).dimensions,{width:I,height:p,aspectRatio:i}=o?L(u,o):u;if(e||(a?(e=Math.round(a*i),a=void 0):e=Math.round(I/2)),t==="cover"&&(!e||!a||e/a===i)?t="contain":t==="contain"&&a&&(e=Math.min(e,Math.round(a*i)),a=void 0),e>I||a&&a>p){let c=a?e/a:i;c>=i?(e=I,a=a&&Math.round(e/c)):(a=p,e=Math.round(a*c))}let s={w:e,q:75,...m};if(s.fm||(s.auto="format"),o&&(s.rect=V(u,o)),t==="cover")if(s.fit="crop",a&&(s.h=a),n){let c=o?n.x/(1-o.left-o.right):n.x,l=o?n.y/(1-o.top-o.bottom):n.y;s["fp-x"]=W(A(c,0,1),3),s["fp-y"]=W(A(l,0,1),3)}else s.crop="entropy";else s.fit="max";if(d){let c=a||Math.round(e/i);s.metadata={sourceDimensions:u,outputDimensions:{width:e,height:c,aspectRatio:e/c}}}return s},A=(r,t,e)=>Math.max(t,Math.min(e,r)),W=(r,t)=>Math.round(r*Math.pow(10,t))/Math.pow(10,t),L=(r,t)=>{if(t.left+t.right>=1||t.top+t.bottom>=1)throw new Error(`Invalid crop: ${JSON.stringify(t)}; crop values must be less than 1`);let e=Math.round(r.width*(1-t.left-t.right)),a=Math.round(r.height*(1-t.top-t.bottom)),n=e/a;return{width:e,height:a,aspectRatio:n}},V=(r,t)=>{let{width:e,height:a}=L(r,t);return[Math.round(t.left*r.width),Math.round(t.top*r.height),e,a].join(",")},O=r=>new URLSearchParams(Object.entries(r).sort(([e],[a])=>e.localeCompare(a)).map(([e,a])=>[e,String(a)])).toString().replace(/%2C/g,",");var g=T(require("react")),S=({as:r,preview:t,...e})=>{let[a,n]=(0,g.useState)(!1),o=(0,g.useRef)(null),m=()=>{n(!0)};(0,g.useEffect)(()=>{var u;(u=o.current)!=null&&u.complete&&m()});let d=r||"img";return g.default.createElement(g.default.Fragment,null,!a&&g.default.createElement(d,{alt:e.alt,className:e.className,"data-lqip":!0,height:e.height,id:e.id,src:t,style:e.style,width:e.width}),g.default.createElement(d,{"data-loading":a?null:!0,onLoad:m,ref:o,style:a?void 0:{height:"10px !important",opacity:0,pointerEvents:"none",position:"absolute",userSelect:"none",width:"10px !important",zIndex:-10},...e}))};var $=T(require("react"));var j=({as:r,baseUrl:t,projectId:e,dataset:a,id:n,hotspot:o,crop:m,width:d,height:u,mode:I="contain",preview:p,htmlWidth:i,htmlHeight:s,htmlId:c,queryParams:l,...b})=>{var N,Q;if(!n)throw new Error("Missing required `id` prop for <SanityImage>.");if(!t&&(!e||!a))throw new Error("Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.");t=t!=null?t:`https://cdn.sanity.io/images/${e}/${a}/`;let E=n.endsWith("-svg"),C=p&&!E?S:r!=null?r:"img",f={alt:(N=b.alt)!=null?N:"",loading:(Q=b.loading)!=null?Q:"lazy",id:c,...b};if(E)return $.default.createElement(C,{...q({id:n,baseUrl:t}),...f});let v={baseUrl:t,id:n,crop:m,hotspot:o,width:d,height:u,mode:I,queryParams:l},{src:z,...D}=h(v);return f.srcSet=x(v).join(", "),f.src=z,f.width=i!=null?i:D.width,f.height=s!=null?s:D.height,p&&(f.as=r!=null?r:"img",f.preview=p),$.default.createElement(C,{...f})};0&&(module.exports={ImageWithPreview,SanityImage,buildSrc,buildSrcSet,parseImageId});
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/index.ts", "../../src/parseImageId.ts", "../../src/urlBuilder.ts", "../../src/ImageWithPreview.tsx", "../../src/SanityImage.tsx"],
|
|
4
|
-
"sourcesContent": ["export { buildSrc, buildSrcSet } from \"./urlBuilder\"\nexport { ImageWithPreview } from \"./ImageWithPreview\"\nexport { parseImageId } from \"./parseImageId\"\nexport { SanityImage } from \"./SanityImage\"\n", "import type { ImageIdParts } from \"./types\"\n\nexport const SANITY_IMAGE_ID_PATTERN = /^image-([\\da-f]+)-(\\d+x\\d+)-(\\w+)$/\n\n/**\n * Parse an image id string into its component parts.\n *\n * @param {string} id The image id string to parse in the format `image-<hash>-<width>x<height>.<ext>`\n * @returns {ImageIdParts} An object containing the asset ID, dimensions, and format\n */\nexport const parseImageId = (id: string): ImageIdParts => {\n const match = SANITY_IMAGE_ID_PATTERN.exec(id)\n const [, assetId, dimensions, format] = match ?? []\n\n if (!match || !assetId || !dimensions || !format) {\n throw new Error(`Could not parse image ID \"${id}\"`)\n }\n\n const [width, height] = dimensions\n .split(\"x\")\n .map((value: string): number => Number.parseInt(value, 10))\n\n if (Number.isNaN(width) || Number.isNaN(height) || !width || !height) {\n throw new Error(`Invalid dimensions \"${dimensions}\"`)\n }\n\n return {\n assetId,\n dimensions: { height, width, aspectRatio: width / height },\n format,\n }\n}\n\n/**\n * Convert an image id to a URL path segment for the Sanity Image API. Input is\n * not validated.\n *\n * @example\n * imageIdToUrlPath(\"image-<hash>-<width>x<height>-<ext>\")\n * // => \"<hash>-<width>x<height>.<ext>\"\n */\nexport const imageIdToUrlPath = (id: string): string => {\n // This can be implemented with `parseImageId` but it's more computationally expensive\n // than this more naive implementation.\n\n const formatSeparatorIndex = id.lastIndexOf(\"-\")\n\n return (\n id.slice(6, formatSeparatorIndex) + \".\" + id.slice(formatSeparatorIndex + 1)\n )\n}\n", "import { imageIdToUrlPath, parseImageId } from \"./parseImageId\"\nimport type {\n ComputedImageData,\n CropData,\n ImageIdParts,\n ImageQueryInputs,\n ImageQueryParams,\n ImageSrcInputs,\n} from \"types\"\n\n/**\n * Convert ImageSrcInputs into a full image URL and computed output dimensions.\n */\nexport const buildSrc = ({\n baseUrl,\n ...inputParams\n}: ImageSrcInputs): ComputedImageData => {\n const { metadata, ...queryParams } = buildQueryParams({\n ...inputParams,\n options: { includeMetadata: true },\n })\n\n // Narrowing for TS\n if (!metadata) {\n throw new Error(\"Missing image output metadata\")\n }\n\n const imageUrl = `${baseUrl}${imageIdToUrlPath(inputParams.id)}`\n\n return {\n src: `${imageUrl}?${buildQueryString(queryParams)}`,\n width: metadata.outputDimensions.width,\n height: metadata.outputDimensions.height,\n }\n}\n\nexport const buildSrcSet = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n baseUrl,\n}: ImageSrcInputs) => {\n // Determine base computed width\n const { w, h } = buildQueryParams({ id, mode, width, height, hotspot, crop })\n\n // URL of the image without any query parameters\n const imageUrl = `${baseUrl}${imageIdToUrlPath(id)}`\n\n // Build srcset\n const srcSetEntries = dynamicMultipliers(w)\n .map((multiple) => {\n const computedWidth = Math.round(w * multiple)\n const computedHeight = h && Math.round(h * multiple)\n\n // Ignore tiny entries; the extra data in the HTML is almost never worth it\n if (multiple < 1 && computedWidth < 50) return null\n\n const params: Omit<ImageQueryParams, \"metadata\"> = buildQueryParams({\n id,\n mode,\n width: computedWidth,\n height: computedHeight,\n hotspot,\n crop,\n })\n\n return `${imageUrl}?${buildQueryString(params)} ${params.w}w`\n })\n .filter(Boolean)\n\n return Array.from(new Set(srcSetEntries))\n}\n\nexport const buildSvgAttributes = ({ id, baseUrl }: ImageSrcInputs) => {\n const { assetId, dimensions, format } = parseImageId(id)\n\n return {\n src: `${baseUrl}${assetId}-${dimensions.width}x${dimensions.height}.${format}`,\n width: dimensions.width,\n height: dimensions.height,\n }\n}\n\nconst dynamicMultipliers = (width: number) => {\n // For really small images, use larger steps\n if (width < 160) {\n return [0.5, 1, 2]\n }\n\n // For typical width images, use standard steps\n if (width < 750) {\n return [0.5, 1, 1.5, 2]\n }\n\n // For larger images, include 0.25x and 0.75x steps\n if (width < 1400) {\n return [0.25, 0.5, 0.75, 1, 1.5, 2]\n }\n\n // For really large images, use a wider range of steps at the low end, and smaller steps at the high end\n return [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]\n}\n\n/**\n * Constructs a query parameters object for the Sanity image URL based on the inputs provided.\n */\nexport const buildQueryParams = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n queryParams,\n options: { includeMetadata = false } = {},\n}: ImageQueryInputs & {\n options?: {\n /** Include data about the image in the response */\n includeMetadata?: boolean\n }\n}): ImageQueryParams => {\n const sourceDimensions = parseImageId(id).dimensions\n\n // If crop is provided, compute post-crop dimensions\n const {\n width: maxWidth,\n height: maxHeight,\n aspectRatio: sourceAspectRatio,\n } = crop ? croppedImageSize(sourceDimensions, crop) : sourceDimensions\n\n // Determine width if not provided\n if (!width) {\n if (height) {\n // Compute width based on height and default ratio\n width = Math.round(height * sourceAspectRatio)\n\n // Discard `height` since we have to be in `contain` mode and we've converted it into `width`\n height = undefined\n } else {\n // Use 1/2 of the max image width by default to allow for 2x scale-up\n width = Math.round(maxWidth / 2)\n }\n }\n\n // Override `cover` mode if both width and height haven't been provided, or if\n // the requested aspect ratio matches the source aspect ratio. In these cases\n // the result will be the same as `contain` mode anyways, and `contain` mode\n // is simpler and saves a few bytes in the URL.\n if (\n mode === \"cover\" &&\n (!width || !height || width / height === sourceAspectRatio)\n ) {\n mode = \"contain\"\n } else if (mode === \"contain\" && height) {\n // Similarly, if `contain` mode is used and a height is provided, we can\n // convert it into a width by adjusting the width such that the\n // aspect-ratio\u2013constrained result will respect the height provided.\n width = Math.min(width, Math.round(height * sourceAspectRatio))\n height = undefined\n }\n\n // Clamp min and max dimensions while preserving requested aspect ratio\n if (width > maxWidth || (height && height > maxHeight)) {\n const requestedAspectRatio = height ? width / height : sourceAspectRatio\n\n if (requestedAspectRatio >= sourceAspectRatio) {\n // Clamp width\n width = maxWidth\n height = height && Math.round(width / requestedAspectRatio)\n } else {\n // Clamp height\n height = maxHeight\n width = Math.round(height * requestedAspectRatio)\n }\n }\n\n // Note: when converting params to a query string initially, we need to\n // use an object or map instead of URLSearchParams, since the latter will\n // allow multiple params with the same name, which is not supported by the\n // Sanity Image API.\n const params: Partial<ImageQueryParams> = {\n w: width,\n q: 75,\n ...queryParams,\n }\n\n // If an explicit format has not been requested, use auto format\n if (!params.fm) params.auto = \"format\"\n\n if (crop) {\n // Convert crop to rect param)\n params.rect = buildRect(sourceDimensions, crop)\n }\n\n if (mode === \"cover\") {\n params.fit = \"crop\"\n\n if (height) {\n params.h = height\n }\n\n if (hotspot) {\n // Hotspot is relative to post-`rect` dimensions; if `crop` is present,\n // the hotspot inputs need to be adjusted accordingly\n const x = crop ? hotspot.x / (1 - crop.left - crop.right) : hotspot.x\n const y = crop ? hotspot.y / (1 - crop.top - crop.bottom) : hotspot.y\n\n params[\"fp-x\"] = roundWithPrecision(clamp(x, 0, 1), 3)\n params[\"fp-y\"] = roundWithPrecision(clamp(y, 0, 1), 3)\n } else {\n // If no hotspot is provided, use Sanity\u2019s `entropy` crop mode\n params.crop = \"entropy\"\n }\n } else {\n params.fit = \"max\"\n }\n\n if (includeMetadata) {\n // Height will be set if the aspect ratio varies from `sourceAspectRatio`\n const outputHeight = height || Math.round(width / sourceAspectRatio)\n\n params.metadata = {\n sourceDimensions,\n outputDimensions: {\n width,\n height: outputHeight,\n aspectRatio: width / outputHeight,\n },\n }\n }\n\n return <ImageQueryParams>params\n}\n\nconst clamp = (value: number, min: number, max: number) =>\n Math.max(min, Math.min(max, value))\nconst roundWithPrecision = (value: number, precision: number) =>\n Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision)\n\nexport const croppedImageSize = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n): ImageIdParts[\"dimensions\"] => {\n if (crop.left + crop.right >= 1 || crop.top + crop.bottom >= 1) {\n throw new Error(\n `Invalid crop: ${JSON.stringify(crop)}; crop values must be less than 1`\n )\n }\n\n const width = Math.round(dimensions.width * (1 - crop.left - crop.right))\n const height = Math.round(dimensions.height * (1 - crop.top - crop.bottom))\n const aspectRatio = width / height\n\n return { width, height, aspectRatio }\n}\n\n/**\n * Build a `rect` value to crop the image.\n */\nexport const buildRect = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n) => {\n const { width, height } = croppedImageSize(dimensions, crop)\n\n return [\n Math.round(crop.left * dimensions.width),\n Math.round(crop.top * dimensions.height),\n width,\n height,\n ].join(\",\")\n}\n\nexport const buildQueryString = (params: Record<string, string | number>) =>\n Object.entries(params)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(value)}`\n )\n .join(\"&\")\n .replace(/%2C/g, \",\") // don't urlencode commas\n", "import React, { useEffect, useRef, useState } from \"react\"\nimport type { ImageWithPreviewProps } from \"./types\"\n\n/**\n * Renders two image tags, one with the preview image and one with the full\n * image. When the full image is loaded, the preview image is removed, revealing\n * the full image.\n */\nexport const ImageWithPreview = <T extends React.ElementType = \"img\">({\n as,\n preview,\n ...props\n}: ImageWithPreviewProps<T>) => {\n const [loaded, setLoaded] = useState(false)\n const ref = useRef<HTMLImageElement>(null)\n\n const onLoad = () => {\n setLoaded(true)\n }\n\n useEffect(() => {\n if (ref.current?.complete) {\n onLoad()\n }\n })\n\n const Img = as || \"img\"\n\n return (\n <>\n {!loaded && (\n <Img\n alt={props.alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={props.style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? undefined\n : {\n // must be > 4px to be lazy loaded\n height: \"10px !important\",\n\n // must be > 4px to be lazy loaded\n opacity: 0,\n\n pointerEvents: \"none\",\n // Cannot use negative x or y values, visibility: hidden, or display: none\n // to hide or the image might not get loaded.\n position: \"absolute\",\n userSelect: \"none\",\n width: \"10px !important\",\n zIndex: -10,\n }\n }\n {...props}\n />\n </>\n )\n}\n", "import React, {\n type ReactElement,\n type ElementType,\n type ComponentPropsWithoutRef,\n} from \"react\"\nimport type { PolymorphicComponentProps, SanityImageProps } from \"./types\"\nimport { buildSrc, buildSrcSet, buildSvgAttributes } from \"./urlBuilder\"\nimport { ImageWithPreview } from \"./ImageWithPreview\"\n\nexport const SanityImage = <C extends ElementType = \"img\">({\n as: component,\n\n // Sanity url\n baseUrl,\n projectId,\n dataset,\n\n // Image definition data\n id,\n hotspot,\n crop,\n width,\n height,\n mode = \"contain\",\n\n // Data for LQIP (preview image)\n preview,\n\n // Native-behavior overrides\n htmlWidth,\n htmlHeight,\n htmlId,\n\n // Image query string params\n queryParams,\n\n // Any remaining props are passed through to the rendered component\n ...rest\n}: PolymorphicComponentProps<C, SanityImageProps>): ReactElement => {\n if (!id) throw new Error(\"Missing required `id` prop for <SanityImage>.\")\n if (!baseUrl && (!projectId || !dataset))\n throw new Error(\n \"Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.\"\n )\n\n baseUrl = baseUrl ?? `https://cdn.sanity.io/images/${projectId}/${dataset}/`\n\n const isSvg = id.endsWith(\"-svg\")\n\n const ImageComponent =\n preview && !isSvg ? ImageWithPreview : component ?? \"img\"\n\n const componentProps: ComponentPropsWithoutRef<typeof ImageComponent> = {\n alt: rest.alt ?? \"\",\n loading: rest.loading ?? \"lazy\",\n id: htmlId,\n ...rest,\n }\n\n if (isSvg) {\n // Sanity ignores all transformations for SVGs, so we can just render the\n // component without passing a query string and without doing anything for\n // the preview.\n return (\n <ImageComponent\n {...buildSvgAttributes({ id, baseUrl })}\n {...componentProps}\n />\n )\n }\n\n // Create default src and build srcSet\n const srcParams = {\n baseUrl,\n id,\n crop,\n hotspot,\n width,\n height,\n mode,\n queryParams,\n }\n\n const { src, ...outputDimensions } = buildSrc(srcParams)\n componentProps.srcSet = buildSrcSet(srcParams).join(\", \")\n componentProps.src = src\n componentProps.width = htmlWidth ?? outputDimensions.width\n componentProps.height = htmlHeight ?? outputDimensions.height\n\n if (preview) {\n componentProps.as = component ?? \"img\"\n componentProps.preview = preview\n }\n\n return <ImageComponent {...componentProps} />\n}\n"],
|
|
5
|
-
"mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,EAAA,gBAAAC,EAAA,aAAAC,EAAA,gBAAAC,EAAA,iBAAAC,IAAA,eAAAC,EAAAP,GCEO,IAAMQ,EAA0B,qCAQ1BC,EAAgBC,GAA6B,CACxD,IAAMC,EAAQH,EAAwB,KAAKE,CAAE,EACvC,CAAC,CAAEE,EAASC,EAAYC,CAAM,EAAIH,GAAA,KAAAA,EAAS,CAAC,EAElD,GAAI,CAACA,GAAS,CAACC,GAAW,CAACC,GAAc,CAACC,EACxC,MAAM,IAAI,MAAM,6BAA6BJ,IAAK,EAGpD,GAAM,CAACK,EAAOC,CAAM,EAAIH,EACrB,MAAM,GAAG,EACT,IAAKI,GAA0B,OAAO,SAASA,EAAO,EAAE,CAAC,EAE5D,GAAI,OAAO,MAAMF,CAAK,GAAK,OAAO,MAAMC,CAAM,GAAK,CAACD,GAAS,CAACC,EAC5D,MAAM,IAAI,MAAM,uBAAuBH,IAAa,EAGtD,MAAO,CACL,QAAAD,EACA,WAAY,CAAE,OAAAI,EAAQ,MAAAD,EAAO,YAAaA,EAAQC,CAAO,EACzD,OAAAF,CACF,CACF,EAUaI,EAAoBR,GAAuB,CAItD,IAAMS,EAAuBT,EAAG,YAAY,GAAG,EAE/C,OACEA,EAAG,MAAM,EAAGS,CAAoB,EAAI,IAAMT,EAAG,MAAMS,EAAuB,CAAC,CAE/E,ECrCO,IAAMC,EAAW,CAAC,CACvB,QAAAC,EACA,GAAGC,CACL,IAAyC,CACvC,GAAM,CAAE,SAAAC,EAAU,GAAGC,CAAY,EAAIC,EAAiB,CACpD,GAAGH,EACH,QAAS,CAAE,gBAAiB,EAAK,CACnC,CAAC,EAGD,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,+BAA+B,EAKjD,MAAO,CACL,IAAK,GAHU,GAAGF,IAAUK,EAAiBJ,EAAY,EAAE,OAGvCK,EAAiBH,CAAW,IAChD,MAAOD,EAAS,iBAAiB,MACjC,OAAQA,EAAS,iBAAiB,MACpC,CACF,EAEaK,EAAc,CAAC,CAC1B,GAAAC,EACA,KAAAC,EAAO,UACP,MAAAC,EACA,OAAAC,EACA,QAAAC,EACA,KAAAC,EACA,QAAAb,CACF,
|
|
6
|
-
"names": ["src_exports", "__export", "ImageWithPreview", "SanityImage", "buildSrc", "buildSrcSet", "parseImageId", "__toCommonJS", "SANITY_IMAGE_ID_PATTERN", "parseImageId", "id", "match", "assetId", "dimensions", "format", "width", "height", "value", "imageIdToUrlPath", "formatSeparatorIndex", "buildSrc", "baseUrl", "inputParams", "metadata", "queryParams", "buildQueryParams", "imageIdToUrlPath", "buildQueryString", "buildSrcSet", "id", "mode", "width", "height", "hotspot", "crop", "w", "h", "imageUrl", "srcSetEntries", "dynamicMultipliers", "multiple", "computedWidth", "computedHeight", "params", "buildSvgAttributes", "assetId", "dimensions", "format", "parseImageId", "includeMetadata", "sourceDimensions", "maxWidth", "maxHeight", "sourceAspectRatio", "croppedImageSize", "requestedAspectRatio", "buildRect", "x", "y", "roundWithPrecision", "clamp", "outputHeight", "value", "min", "max", "precision", "aspectRatio", "a", "b", "key", "import_react", "ImageWithPreview", "as", "preview", "props", "loaded", "setLoaded", "ref", "onLoad", "_a", "Img", "React", "import_react", "SanityImage", "component", "baseUrl", "projectId", "dataset", "id", "hotspot", "crop", "width", "height", "mode", "preview", "htmlWidth", "htmlHeight", "htmlId", "queryParams", "rest", "_a", "_b", "isSvg", "ImageComponent", "ImageWithPreview", "componentProps", "React", "buildSvgAttributes", "srcParams", "src", "outputDimensions", "buildSrc", "buildSrcSet"]
|
|
4
|
+
"sourcesContent": ["export { buildSrc, buildSrcSet } from \"./urlBuilder\"\nexport { ImageWithPreview } from \"./ImageWithPreview\"\nexport { parseImageId } from \"./parseImageId\"\nexport { SanityImage } from \"./SanityImage\"\n", "import type { ImageIdParts } from \"./types\"\n\nexport const SANITY_IMAGE_ID_PATTERN = /^image-([\\da-f]+)-(\\d+x\\d+)-(\\w+)$/\n\n/**\n * Parse an image id string into its component parts.\n *\n * @param {string} id The image id string to parse in the format `image-<hash>-<width>x<height>.<ext>`\n * @returns {ImageIdParts} An object containing the asset ID, dimensions, and format\n */\nexport const parseImageId = (id: string): ImageIdParts => {\n const match = SANITY_IMAGE_ID_PATTERN.exec(id)\n const [, assetId, dimensions, format] = match ?? []\n\n if (!match || !assetId || !dimensions || !format) {\n throw new Error(`Could not parse image ID \"${id}\"`)\n }\n\n const [width, height] = dimensions\n .split(\"x\")\n .map((value: string): number => Number.parseInt(value, 10))\n\n if (Number.isNaN(width) || Number.isNaN(height) || !width || !height) {\n throw new Error(`Invalid dimensions \"${dimensions}\"`)\n }\n\n return {\n assetId,\n dimensions: { height, width, aspectRatio: width / height },\n format,\n }\n}\n\n/**\n * Convert an image id to a URL path segment for the Sanity Image API. Input is\n * not validated.\n *\n * @example\n * imageIdToUrlPath(\"image-<hash>-<width>x<height>-<ext>\")\n * // => \"<hash>-<width>x<height>.<ext>\"\n */\nexport const imageIdToUrlPath = (id: string): string => {\n // This can be implemented with `parseImageId` but it's more computationally expensive\n // than this more naive implementation.\n\n const formatSeparatorIndex = id.lastIndexOf(\"-\")\n\n return (\n id.slice(6, formatSeparatorIndex) + \".\" + id.slice(formatSeparatorIndex + 1)\n )\n}\n", "import { imageIdToUrlPath, parseImageId } from \"./parseImageId\"\nimport type {\n ComputedImageData,\n CropData,\n ImageIdParts,\n ImageQueryInputs,\n ImageQueryParams,\n ImageSrcInputs,\n} from \"./types\"\n\n/**\n * Convert ImageSrcInputs into a full image URL and computed output dimensions.\n */\nexport const buildSrc = ({\n baseUrl,\n ...inputParams\n}: ImageSrcInputs): ComputedImageData => {\n const { metadata, ...queryParams } = buildQueryParams({\n ...inputParams,\n options: { includeMetadata: true },\n })\n\n // Narrowing for TS\n if (!metadata) {\n throw new Error(\"Missing image output metadata\")\n }\n\n const imageUrl = `${baseUrl}${imageIdToUrlPath(inputParams.id)}`\n\n return {\n src: `${imageUrl}?${buildQueryString(queryParams)}`,\n width: metadata.outputDimensions.width,\n height: metadata.outputDimensions.height,\n }\n}\n\nexport const buildSrcSet = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n baseUrl,\n}: ImageSrcInputs): string[] => {\n // Determine base computed width\n const { w, h } = buildQueryParams({ id, mode, width, height, hotspot, crop })\n\n // URL of the image without any query parameters\n const imageUrl = `${baseUrl}${imageIdToUrlPath(id)}`\n\n // Build srcset\n const srcSetEntries: string[] = dynamicMultipliers(w)\n .map((multiple) => {\n const computedWidth = Math.round(w * multiple)\n const computedHeight = h && Math.round(h * multiple)\n\n // Ignore tiny entries; the extra data in the HTML is almost never worth it\n if (multiple < 1 && computedWidth < 50) return null\n\n const params: Omit<ImageQueryParams, \"metadata\"> = buildQueryParams({\n id,\n mode,\n width: computedWidth,\n height: computedHeight,\n hotspot,\n crop,\n })\n\n return `${imageUrl}?${buildQueryString(params)} ${params.w}w`\n })\n .filter((entry): entry is string => Boolean(entry))\n\n return Array.from(new Set(srcSetEntries))\n}\n\nexport const buildSvgAttributes = ({ id, baseUrl }: ImageSrcInputs) => {\n const { assetId, dimensions, format } = parseImageId(id)\n\n return {\n src: `${baseUrl}${assetId}-${dimensions.width}x${dimensions.height}.${format}`,\n width: dimensions.width,\n height: dimensions.height,\n }\n}\n\nconst dynamicMultipliers = (width: number): number[] => {\n // For really small images, use larger steps\n if (width < 160) {\n return [0.5, 1, 2]\n }\n\n // For typical width images, use standard steps\n if (width < 750) {\n return [0.5, 1, 1.5, 2]\n }\n\n // For larger images, include 0.25x and 0.75x steps\n if (width < 1400) {\n return [0.25, 0.5, 0.75, 1, 1.5, 2]\n }\n\n // For really large images, use a wider range of steps at the low end, and smaller steps at the high end\n return [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]\n}\n\n/**\n * Constructs a query parameters object for the Sanity image URL based on the inputs provided.\n */\nexport const buildQueryParams = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n queryParams,\n options: { includeMetadata = false } = {},\n}: ImageQueryInputs & {\n options?: {\n /** Include data about the image in the response */\n includeMetadata?: boolean\n }\n}): ImageQueryParams => {\n const sourceDimensions = parseImageId(id).dimensions\n\n // If crop is provided, compute post-crop dimensions\n const {\n width: maxWidth,\n height: maxHeight,\n aspectRatio: sourceAspectRatio,\n } = crop ? croppedImageSize(sourceDimensions, crop) : sourceDimensions\n\n // Determine width if not provided\n if (!width) {\n if (height) {\n // Compute width based on height and default ratio\n width = Math.round(height * sourceAspectRatio)\n\n // Discard `height` since we have to be in `contain` mode and we've converted it into `width`\n height = undefined\n } else {\n // Use 1/2 of the max image width by default to allow for 2x scale-up\n width = Math.round(maxWidth / 2)\n }\n }\n\n // Override `cover` mode if both width and height haven't been provided, or if\n // the requested aspect ratio matches the source aspect ratio. In these cases\n // the result will be the same as `contain` mode anyways, and `contain` mode\n // is simpler and saves a few bytes in the URL.\n if (\n mode === \"cover\" &&\n (!width || !height || width / height === sourceAspectRatio)\n ) {\n mode = \"contain\"\n } else if (mode === \"contain\" && height) {\n // Similarly, if `contain` mode is used and a height is provided, we can\n // convert it into a width by adjusting the width such that the\n // aspect-ratio\u2013constrained result will respect the height provided.\n width = Math.min(width, Math.round(height * sourceAspectRatio))\n height = undefined\n }\n\n // Clamp min and max dimensions while preserving requested aspect ratio\n if (width > maxWidth || (height && height > maxHeight)) {\n const requestedAspectRatio = height ? width / height : sourceAspectRatio\n\n if (requestedAspectRatio >= sourceAspectRatio) {\n // Clamp width\n width = maxWidth\n height = height && Math.round(width / requestedAspectRatio)\n } else {\n // Clamp height\n height = maxHeight\n width = Math.round(height * requestedAspectRatio)\n }\n }\n\n // Note: when converting params to a query string initially, we need to\n // use an object or map instead of URLSearchParams, since the latter will\n // allow multiple params with the same name, which is not supported by the\n // Sanity Image API.\n const params: Partial<ImageQueryParams> = {\n w: width,\n q: 75,\n ...queryParams,\n }\n\n // If an explicit format has not been requested, use auto format\n if (!params.fm) params.auto = \"format\"\n\n if (crop) {\n // Convert crop to rect param)\n params.rect = buildRect(sourceDimensions, crop)\n }\n\n if (mode === \"cover\") {\n params.fit = \"crop\"\n\n if (height) {\n params.h = height\n }\n\n if (hotspot) {\n // Hotspot is relative to post-`rect` dimensions; if `crop` is present,\n // the hotspot inputs need to be adjusted accordingly\n const x = crop ? hotspot.x / (1 - crop.left - crop.right) : hotspot.x\n const y = crop ? hotspot.y / (1 - crop.top - crop.bottom) : hotspot.y\n\n params[\"fp-x\"] = roundWithPrecision(clamp(x, 0, 1), 3)\n params[\"fp-y\"] = roundWithPrecision(clamp(y, 0, 1), 3)\n } else {\n // If no hotspot is provided, use Sanity\u2019s `entropy` crop mode\n params.crop = \"entropy\"\n }\n } else {\n params.fit = \"max\"\n }\n\n if (includeMetadata) {\n // Height will be set if the aspect ratio varies from `sourceAspectRatio`\n const outputHeight = height || Math.round(width / sourceAspectRatio)\n\n params.metadata = <ImageQueryParams[\"metadata\"]>{\n sourceDimensions,\n outputDimensions: {\n width,\n height: outputHeight,\n aspectRatio: width / outputHeight,\n },\n }\n }\n\n return <ImageQueryParams>params\n}\n\nconst clamp = (value: number, min: number, max: number): number =>\n Math.max(min, Math.min(max, value))\n\nconst roundWithPrecision = (value: number, precision: number): number =>\n Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision)\n\nexport const croppedImageSize = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n): ImageIdParts[\"dimensions\"] => {\n if (crop.left + crop.right >= 1 || crop.top + crop.bottom >= 1) {\n throw new Error(\n `Invalid crop: ${JSON.stringify(crop)}; crop values must be less than 1`\n )\n }\n\n const width = Math.round(dimensions.width * (1 - crop.left - crop.right))\n const height = Math.round(dimensions.height * (1 - crop.top - crop.bottom))\n const aspectRatio = width / height\n\n return { width, height, aspectRatio }\n}\n\n/**\n * Build a `rect` value to crop the image.\n */\nexport const buildRect = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n): string => {\n const { width, height } = croppedImageSize(dimensions, crop)\n\n return [\n Math.round(crop.left * dimensions.width),\n Math.round(crop.top * dimensions.height),\n width,\n height,\n ].join(\",\")\n}\n\n/**\n * Converts an object of query params into a query string. The keys are sorted\n * alphabetically to maximize cache-hit rates. Commas are not URL-encoded since\n * doing so is unnecessary, adds extra data, and makes it harder to read.\n */\nexport const buildQueryString = (\n params: Partial<{\n [K in keyof Omit<ImageQueryParams, \"metadata\">]: ImageQueryParams[K]\n }>\n): string => {\n const searchParams = new URLSearchParams(\n Object.entries(params)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, value]) => [key, String(value)])\n )\n\n return searchParams.toString().replace(/%2C/g, \",\") // don't urlencode commas\n}\n", "import React, { useEffect, useRef, useState } from \"react\"\nimport type { ImageWithPreviewProps } from \"./types\"\n\n/**\n * Renders two image tags, one with the preview image and one with the full\n * image. When the full image is loaded, the preview image is removed, revealing\n * the full image.\n */\nexport const ImageWithPreview = <T extends React.ElementType = \"img\">({\n as,\n preview,\n ...props\n}: ImageWithPreviewProps<T>) => {\n const [loaded, setLoaded] = useState(false)\n const ref = useRef<HTMLImageElement>(null)\n\n const onLoad = () => {\n setLoaded(true)\n }\n\n useEffect(() => {\n if (ref.current?.complete) {\n onLoad()\n }\n })\n\n const Img = as || \"img\"\n\n return (\n <>\n {!loaded && (\n <Img\n alt={props.alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={props.style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? undefined\n : {\n // must be > 4px to be lazy loaded\n height: \"10px !important\",\n\n // must be > 4px to be lazy loaded\n opacity: 0,\n\n pointerEvents: \"none\",\n // Cannot use negative x or y values, visibility: hidden, or display: none\n // to hide or the image might not get loaded.\n position: \"absolute\",\n userSelect: \"none\",\n width: \"10px !important\",\n zIndex: -10,\n }\n }\n {...props}\n />\n </>\n )\n}\n", "import React, {\n type ReactElement,\n type ElementType,\n type ComponentPropsWithoutRef,\n} from \"react\"\nimport type { PolymorphicComponentProps, SanityImageProps } from \"./types\"\nimport { buildSrc, buildSrcSet, buildSvgAttributes } from \"./urlBuilder\"\nimport { ImageWithPreview } from \"./ImageWithPreview\"\n\nexport const SanityImage = <C extends ElementType = \"img\">({\n as: component,\n\n // Sanity url\n baseUrl,\n projectId,\n dataset,\n\n // Image definition data\n id,\n hotspot,\n crop,\n width,\n height,\n mode = \"contain\",\n\n // Data for LQIP (preview image)\n preview,\n\n // Native-behavior overrides\n htmlWidth,\n htmlHeight,\n htmlId,\n\n // Image query string params\n queryParams,\n\n // Any remaining props are passed through to the rendered component\n ...rest\n}: PolymorphicComponentProps<C, SanityImageProps>): ReactElement => {\n if (!id) throw new Error(\"Missing required `id` prop for <SanityImage>.\")\n if (!baseUrl && (!projectId || !dataset))\n throw new Error(\n \"Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.\"\n )\n\n baseUrl = baseUrl ?? `https://cdn.sanity.io/images/${projectId}/${dataset}/`\n\n const isSvg = id.endsWith(\"-svg\")\n\n const ImageComponent =\n preview && !isSvg ? ImageWithPreview : component ?? \"img\"\n\n const componentProps: ComponentPropsWithoutRef<typeof ImageComponent> = {\n alt: rest.alt ?? \"\",\n loading: rest.loading ?? \"lazy\",\n id: htmlId,\n ...rest,\n }\n\n if (isSvg) {\n // Sanity ignores all transformations for SVGs, so we can just render the\n // component without passing a query string and without doing anything for\n // the preview.\n return (\n <ImageComponent\n {...buildSvgAttributes({ id, baseUrl })}\n {...componentProps}\n />\n )\n }\n\n // Create default src and build srcSet\n const srcParams = {\n baseUrl,\n id,\n crop,\n hotspot,\n width,\n height,\n mode,\n queryParams,\n }\n\n const { src, ...outputDimensions } = buildSrc(srcParams)\n componentProps.srcSet = buildSrcSet(srcParams).join(\", \")\n componentProps.src = src\n componentProps.width = htmlWidth ?? outputDimensions.width\n componentProps.height = htmlHeight ?? outputDimensions.height\n\n if (preview) {\n componentProps.as = component ?? \"img\"\n componentProps.preview = preview\n }\n\n return <ImageComponent {...componentProps} />\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["src_exports", "__export", "ImageWithPreview", "SanityImage", "buildSrc", "buildSrcSet", "parseImageId", "__toCommonJS", "SANITY_IMAGE_ID_PATTERN", "parseImageId", "id", "match", "assetId", "dimensions", "format", "width", "height", "value", "imageIdToUrlPath", "formatSeparatorIndex", "buildSrc", "baseUrl", "inputParams", "metadata", "queryParams", "buildQueryParams", "imageIdToUrlPath", "buildQueryString", "buildSrcSet", "id", "mode", "width", "height", "hotspot", "crop", "w", "h", "imageUrl", "srcSetEntries", "dynamicMultipliers", "multiple", "computedWidth", "computedHeight", "params", "entry", "buildSvgAttributes", "assetId", "dimensions", "format", "parseImageId", "includeMetadata", "sourceDimensions", "maxWidth", "maxHeight", "sourceAspectRatio", "croppedImageSize", "requestedAspectRatio", "buildRect", "x", "y", "roundWithPrecision", "clamp", "outputHeight", "value", "min", "max", "precision", "aspectRatio", "a", "b", "key", "import_react", "ImageWithPreview", "as", "preview", "props", "loaded", "setLoaded", "ref", "onLoad", "_a", "Img", "React", "import_react", "SanityImage", "component", "baseUrl", "projectId", "dataset", "id", "hotspot", "crop", "width", "height", "mode", "preview", "htmlWidth", "htmlHeight", "htmlId", "queryParams", "rest", "_a", "_b", "isSvg", "ImageComponent", "ImageWithPreview", "componentProps", "React", "buildSvgAttributes", "srcParams", "src", "outputDimensions", "buildSrc", "buildSrcSet"]
|
|
7
7
|
}
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var A=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,I=n=>{let
|
|
1
|
+
var A=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,I=n=>{let r=A.exec(n),[,t,e,a]=r??[];if(!r||!t||!e||!a)throw new Error(`Could not parse image ID "${n}"`);let[o,m]=e.split("x").map(c=>Number.parseInt(c,10));if(Number.isNaN(o)||Number.isNaN(m)||!o||!m)throw new Error(`Invalid dimensions "${e}"`);return{assetId:t,dimensions:{height:m,width:o,aspectRatio:o/m},format:a}},P=n=>{let r=n.lastIndexOf("-");return n.slice(6,r)+"."+n.slice(r+1)};var S=({baseUrl:n,...r})=>{let{metadata:t,...e}=x({...r,options:{includeMetadata:!0}});if(!t)throw new Error("Missing image output metadata");return{src:`${`${n}${P(r.id)}`}?${R(e)}`,width:t.outputDimensions.width,height:t.outputDimensions.height}},b=({id:n,mode:r="contain",width:t,height:e,hotspot:a,crop:o,baseUrl:m})=>{let{w:c,h:p}=x({id:n,mode:r,width:t,height:e,hotspot:a,crop:o}),l=`${m}${P(n)}`,d=q(c).map(s=>{let i=Math.round(c*s),u=p&&Math.round(p*s);if(s<1&&i<50)return null;let f=x({id:n,mode:r,width:i,height:u,hotspot:a,crop:o});return`${l}?${R(f)} ${f.w}w`}).filter(s=>!!s);return Array.from(new Set(d))},N=({id:n,baseUrl:r})=>{let{assetId:t,dimensions:e,format:a}=I(n);return{src:`${r}${t}-${e.width}x${e.height}.${a}`,width:e.width,height:e.height}},q=n=>n<160?[.5,1,2]:n<750?[.5,1,1.5,2]:n<1400?[.25,.5,.75,1,1.5,2]:[.25,.5,.75,1,1.25,1.5,1.75,2],x=({id:n,mode:r="contain",width:t,height:e,hotspot:a,crop:o,queryParams:m,options:{includeMetadata:c=!1}={}})=>{let p=I(n).dimensions,{width:l,height:d,aspectRatio:s}=o?Q(p,o):p;if(t||(e?(t=Math.round(e*s),e=void 0):t=Math.round(l/2)),r==="cover"&&(!t||!e||t/e===s)?r="contain":r==="contain"&&e&&(t=Math.min(t,Math.round(e*s)),e=void 0),t>l||e&&e>d){let u=e?t/e:s;u>=s?(t=l,e=e&&Math.round(t/u)):(e=d,t=Math.round(e*u))}let i={w:t,q:75,...m};if(i.fm||(i.auto="format"),o&&(i.rect=L(p,o)),r==="cover")if(i.fit="crop",e&&(i.h=e),a){let u=o?a.x/(1-o.left-o.right):a.x,f=o?a.y/(1-o.top-o.bottom):a.y;i["fp-x"]=D(v(u,0,1),3),i["fp-y"]=D(v(f,0,1),3)}else i.crop="entropy";else i.fit="max";if(c){let u=e||Math.round(t/s);i.metadata={sourceDimensions:p,outputDimensions:{width:t,height:u,aspectRatio:t/u}}}return i},v=(n,r,t)=>Math.max(r,Math.min(t,n)),D=(n,r)=>Math.round(n*Math.pow(10,r))/Math.pow(10,r),Q=(n,r)=>{if(r.left+r.right>=1||r.top+r.bottom>=1)throw new Error(`Invalid crop: ${JSON.stringify(r)}; crop values must be less than 1`);let t=Math.round(n.width*(1-r.left-r.right)),e=Math.round(n.height*(1-r.top-r.bottom)),a=t/e;return{width:t,height:e,aspectRatio:a}},L=(n,r)=>{let{width:t,height:e}=Q(n,r);return[Math.round(r.left*n.width),Math.round(r.top*n.height),t,e].join(",")},R=n=>new URLSearchParams(Object.entries(n).sort(([t],[e])=>t.localeCompare(e)).map(([t,e])=>[t,String(e)])).toString().replace(/%2C/g,",");import h,{useEffect as O,useRef as H,useState as j}from"react";var w=({as:n,preview:r,...t})=>{let[e,a]=j(!1),o=H(null),m=()=>{a(!0)};O(()=>{o.current?.complete&&m()});let c=n||"img";return h.createElement(h.Fragment,null,!e&&h.createElement(c,{alt:t.alt,className:t.className,"data-lqip":!0,height:t.height,id:t.id,src:r,style:t.style,width:t.width}),h.createElement(c,{"data-loading":e?null:!0,onLoad:m,ref:o,style:e?void 0:{height:"10px !important",opacity:0,pointerEvents:"none",position:"absolute",userSelect:"none",width:"10px !important",zIndex:-10},...t}))};import T from"react";var z=({as:n,baseUrl:r,projectId:t,dataset:e,id:a,hotspot:o,crop:m,width:c,height:p,mode:l="contain",preview:d,htmlWidth:s,htmlHeight:i,htmlId:u,queryParams:f,...y})=>{if(!a)throw new Error("Missing required `id` prop for <SanityImage>.");if(!r&&(!t||!e))throw new Error("Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.");r=r??`https://cdn.sanity.io/images/${t}/${e}/`;let M=a.endsWith("-svg"),$=d&&!M?w:n??"img",g={alt:y.alt??"",loading:y.loading??"lazy",id:u,...y};if(M)return T.createElement($,{...N({id:a,baseUrl:r}),...g});let E={baseUrl:r,id:a,crop:m,hotspot:o,width:c,height:p,mode:l,queryParams:f},{src:W,...C}=S(E);return g.srcSet=b(E).join(", "),g.src=W,g.width=s??C.width,g.height=i??C.height,d&&(g.as=n??"img",g.preview=d),T.createElement($,{...g})};export{w as ImageWithPreview,z as SanityImage,S as buildSrc,b as buildSrcSet,I as parseImageId};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/mjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/parseImageId.ts", "../../src/urlBuilder.ts", "../../src/ImageWithPreview.tsx", "../../src/SanityImage.tsx"],
|
|
4
|
-
"sourcesContent": ["import type { ImageIdParts } from \"./types\"\n\nexport const SANITY_IMAGE_ID_PATTERN = /^image-([\\da-f]+)-(\\d+x\\d+)-(\\w+)$/\n\n/**\n * Parse an image id string into its component parts.\n *\n * @param {string} id The image id string to parse in the format `image-<hash>-<width>x<height>.<ext>`\n * @returns {ImageIdParts} An object containing the asset ID, dimensions, and format\n */\nexport const parseImageId = (id: string): ImageIdParts => {\n const match = SANITY_IMAGE_ID_PATTERN.exec(id)\n const [, assetId, dimensions, format] = match ?? []\n\n if (!match || !assetId || !dimensions || !format) {\n throw new Error(`Could not parse image ID \"${id}\"`)\n }\n\n const [width, height] = dimensions\n .split(\"x\")\n .map((value: string): number => Number.parseInt(value, 10))\n\n if (Number.isNaN(width) || Number.isNaN(height) || !width || !height) {\n throw new Error(`Invalid dimensions \"${dimensions}\"`)\n }\n\n return {\n assetId,\n dimensions: { height, width, aspectRatio: width / height },\n format,\n }\n}\n\n/**\n * Convert an image id to a URL path segment for the Sanity Image API. Input is\n * not validated.\n *\n * @example\n * imageIdToUrlPath(\"image-<hash>-<width>x<height>-<ext>\")\n * // => \"<hash>-<width>x<height>.<ext>\"\n */\nexport const imageIdToUrlPath = (id: string): string => {\n // This can be implemented with `parseImageId` but it's more computationally expensive\n // than this more naive implementation.\n\n const formatSeparatorIndex = id.lastIndexOf(\"-\")\n\n return (\n id.slice(6, formatSeparatorIndex) + \".\" + id.slice(formatSeparatorIndex + 1)\n )\n}\n", "import { imageIdToUrlPath, parseImageId } from \"./parseImageId\"\nimport type {\n ComputedImageData,\n CropData,\n ImageIdParts,\n ImageQueryInputs,\n ImageQueryParams,\n ImageSrcInputs,\n} from \"types\"\n\n/**\n * Convert ImageSrcInputs into a full image URL and computed output dimensions.\n */\nexport const buildSrc = ({\n baseUrl,\n ...inputParams\n}: ImageSrcInputs): ComputedImageData => {\n const { metadata, ...queryParams } = buildQueryParams({\n ...inputParams,\n options: { includeMetadata: true },\n })\n\n // Narrowing for TS\n if (!metadata) {\n throw new Error(\"Missing image output metadata\")\n }\n\n const imageUrl = `${baseUrl}${imageIdToUrlPath(inputParams.id)}`\n\n return {\n src: `${imageUrl}?${buildQueryString(queryParams)}`,\n width: metadata.outputDimensions.width,\n height: metadata.outputDimensions.height,\n }\n}\n\nexport const buildSrcSet = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n baseUrl,\n}: ImageSrcInputs) => {\n // Determine base computed width\n const { w, h } = buildQueryParams({ id, mode, width, height, hotspot, crop })\n\n // URL of the image without any query parameters\n const imageUrl = `${baseUrl}${imageIdToUrlPath(id)}`\n\n // Build srcset\n const srcSetEntries = dynamicMultipliers(w)\n .map((multiple) => {\n const computedWidth = Math.round(w * multiple)\n const computedHeight = h && Math.round(h * multiple)\n\n // Ignore tiny entries; the extra data in the HTML is almost never worth it\n if (multiple < 1 && computedWidth < 50) return null\n\n const params: Omit<ImageQueryParams, \"metadata\"> = buildQueryParams({\n id,\n mode,\n width: computedWidth,\n height: computedHeight,\n hotspot,\n crop,\n })\n\n return `${imageUrl}?${buildQueryString(params)} ${params.w}w`\n })\n .filter(Boolean)\n\n return Array.from(new Set(srcSetEntries))\n}\n\nexport const buildSvgAttributes = ({ id, baseUrl }: ImageSrcInputs) => {\n const { assetId, dimensions, format } = parseImageId(id)\n\n return {\n src: `${baseUrl}${assetId}-${dimensions.width}x${dimensions.height}.${format}`,\n width: dimensions.width,\n height: dimensions.height,\n }\n}\n\nconst dynamicMultipliers = (width: number) => {\n // For really small images, use larger steps\n if (width < 160) {\n return [0.5, 1, 2]\n }\n\n // For typical width images, use standard steps\n if (width < 750) {\n return [0.5, 1, 1.5, 2]\n }\n\n // For larger images, include 0.25x and 0.75x steps\n if (width < 1400) {\n return [0.25, 0.5, 0.75, 1, 1.5, 2]\n }\n\n // For really large images, use a wider range of steps at the low end, and smaller steps at the high end\n return [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]\n}\n\n/**\n * Constructs a query parameters object for the Sanity image URL based on the inputs provided.\n */\nexport const buildQueryParams = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n queryParams,\n options: { includeMetadata = false } = {},\n}: ImageQueryInputs & {\n options?: {\n /** Include data about the image in the response */\n includeMetadata?: boolean\n }\n}): ImageQueryParams => {\n const sourceDimensions = parseImageId(id).dimensions\n\n // If crop is provided, compute post-crop dimensions\n const {\n width: maxWidth,\n height: maxHeight,\n aspectRatio: sourceAspectRatio,\n } = crop ? croppedImageSize(sourceDimensions, crop) : sourceDimensions\n\n // Determine width if not provided\n if (!width) {\n if (height) {\n // Compute width based on height and default ratio\n width = Math.round(height * sourceAspectRatio)\n\n // Discard `height` since we have to be in `contain` mode and we've converted it into `width`\n height = undefined\n } else {\n // Use 1/2 of the max image width by default to allow for 2x scale-up\n width = Math.round(maxWidth / 2)\n }\n }\n\n // Override `cover` mode if both width and height haven't been provided, or if\n // the requested aspect ratio matches the source aspect ratio. In these cases\n // the result will be the same as `contain` mode anyways, and `contain` mode\n // is simpler and saves a few bytes in the URL.\n if (\n mode === \"cover\" &&\n (!width || !height || width / height === sourceAspectRatio)\n ) {\n mode = \"contain\"\n } else if (mode === \"contain\" && height) {\n // Similarly, if `contain` mode is used and a height is provided, we can\n // convert it into a width by adjusting the width such that the\n // aspect-ratio\u2013constrained result will respect the height provided.\n width = Math.min(width, Math.round(height * sourceAspectRatio))\n height = undefined\n }\n\n // Clamp min and max dimensions while preserving requested aspect ratio\n if (width > maxWidth || (height && height > maxHeight)) {\n const requestedAspectRatio = height ? width / height : sourceAspectRatio\n\n if (requestedAspectRatio >= sourceAspectRatio) {\n // Clamp width\n width = maxWidth\n height = height && Math.round(width / requestedAspectRatio)\n } else {\n // Clamp height\n height = maxHeight\n width = Math.round(height * requestedAspectRatio)\n }\n }\n\n // Note: when converting params to a query string initially, we need to\n // use an object or map instead of URLSearchParams, since the latter will\n // allow multiple params with the same name, which is not supported by the\n // Sanity Image API.\n const params: Partial<ImageQueryParams> = {\n w: width,\n q: 75,\n ...queryParams,\n }\n\n // If an explicit format has not been requested, use auto format\n if (!params.fm) params.auto = \"format\"\n\n if (crop) {\n // Convert crop to rect param)\n params.rect = buildRect(sourceDimensions, crop)\n }\n\n if (mode === \"cover\") {\n params.fit = \"crop\"\n\n if (height) {\n params.h = height\n }\n\n if (hotspot) {\n // Hotspot is relative to post-`rect` dimensions; if `crop` is present,\n // the hotspot inputs need to be adjusted accordingly\n const x = crop ? hotspot.x / (1 - crop.left - crop.right) : hotspot.x\n const y = crop ? hotspot.y / (1 - crop.top - crop.bottom) : hotspot.y\n\n params[\"fp-x\"] = roundWithPrecision(clamp(x, 0, 1), 3)\n params[\"fp-y\"] = roundWithPrecision(clamp(y, 0, 1), 3)\n } else {\n // If no hotspot is provided, use Sanity\u2019s `entropy` crop mode\n params.crop = \"entropy\"\n }\n } else {\n params.fit = \"max\"\n }\n\n if (includeMetadata) {\n // Height will be set if the aspect ratio varies from `sourceAspectRatio`\n const outputHeight = height || Math.round(width / sourceAspectRatio)\n\n params.metadata = {\n sourceDimensions,\n outputDimensions: {\n width,\n height: outputHeight,\n aspectRatio: width / outputHeight,\n },\n }\n }\n\n return <ImageQueryParams>params\n}\n\nconst clamp = (value: number, min: number, max: number) =>\n Math.max(min, Math.min(max, value))\nconst roundWithPrecision = (value: number, precision: number) =>\n Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision)\n\nexport const croppedImageSize = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n): ImageIdParts[\"dimensions\"] => {\n if (crop.left + crop.right >= 1 || crop.top + crop.bottom >= 1) {\n throw new Error(\n `Invalid crop: ${JSON.stringify(crop)}; crop values must be less than 1`\n )\n }\n\n const width = Math.round(dimensions.width * (1 - crop.left - crop.right))\n const height = Math.round(dimensions.height * (1 - crop.top - crop.bottom))\n const aspectRatio = width / height\n\n return { width, height, aspectRatio }\n}\n\n/**\n * Build a `rect` value to crop the image.\n */\nexport const buildRect = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n) => {\n const { width, height } = croppedImageSize(dimensions, crop)\n\n return [\n Math.round(crop.left * dimensions.width),\n Math.round(crop.top * dimensions.height),\n width,\n height,\n ].join(\",\")\n}\n\nexport const buildQueryString = (params: Record<string, string | number>) =>\n Object.entries(params)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(value)}`\n )\n .join(\"&\")\n .replace(/%2C/g, \",\") // don't urlencode commas\n", "import React, { useEffect, useRef, useState } from \"react\"\nimport type { ImageWithPreviewProps } from \"./types\"\n\n/**\n * Renders two image tags, one with the preview image and one with the full\n * image. When the full image is loaded, the preview image is removed, revealing\n * the full image.\n */\nexport const ImageWithPreview = <T extends React.ElementType = \"img\">({\n as,\n preview,\n ...props\n}: ImageWithPreviewProps<T>) => {\n const [loaded, setLoaded] = useState(false)\n const ref = useRef<HTMLImageElement>(null)\n\n const onLoad = () => {\n setLoaded(true)\n }\n\n useEffect(() => {\n if (ref.current?.complete) {\n onLoad()\n }\n })\n\n const Img = as || \"img\"\n\n return (\n <>\n {!loaded && (\n <Img\n alt={props.alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={props.style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? undefined\n : {\n // must be > 4px to be lazy loaded\n height: \"10px !important\",\n\n // must be > 4px to be lazy loaded\n opacity: 0,\n\n pointerEvents: \"none\",\n // Cannot use negative x or y values, visibility: hidden, or display: none\n // to hide or the image might not get loaded.\n position: \"absolute\",\n userSelect: \"none\",\n width: \"10px !important\",\n zIndex: -10,\n }\n }\n {...props}\n />\n </>\n )\n}\n", "import React, {\n type ReactElement,\n type ElementType,\n type ComponentPropsWithoutRef,\n} from \"react\"\nimport type { PolymorphicComponentProps, SanityImageProps } from \"./types\"\nimport { buildSrc, buildSrcSet, buildSvgAttributes } from \"./urlBuilder\"\nimport { ImageWithPreview } from \"./ImageWithPreview\"\n\nexport const SanityImage = <C extends ElementType = \"img\">({\n as: component,\n\n // Sanity url\n baseUrl,\n projectId,\n dataset,\n\n // Image definition data\n id,\n hotspot,\n crop,\n width,\n height,\n mode = \"contain\",\n\n // Data for LQIP (preview image)\n preview,\n\n // Native-behavior overrides\n htmlWidth,\n htmlHeight,\n htmlId,\n\n // Image query string params\n queryParams,\n\n // Any remaining props are passed through to the rendered component\n ...rest\n}: PolymorphicComponentProps<C, SanityImageProps>): ReactElement => {\n if (!id) throw new Error(\"Missing required `id` prop for <SanityImage>.\")\n if (!baseUrl && (!projectId || !dataset))\n throw new Error(\n \"Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.\"\n )\n\n baseUrl = baseUrl ?? `https://cdn.sanity.io/images/${projectId}/${dataset}/`\n\n const isSvg = id.endsWith(\"-svg\")\n\n const ImageComponent =\n preview && !isSvg ? ImageWithPreview : component ?? \"img\"\n\n const componentProps: ComponentPropsWithoutRef<typeof ImageComponent> = {\n alt: rest.alt ?? \"\",\n loading: rest.loading ?? \"lazy\",\n id: htmlId,\n ...rest,\n }\n\n if (isSvg) {\n // Sanity ignores all transformations for SVGs, so we can just render the\n // component without passing a query string and without doing anything for\n // the preview.\n return (\n <ImageComponent\n {...buildSvgAttributes({ id, baseUrl })}\n {...componentProps}\n />\n )\n }\n\n // Create default src and build srcSet\n const srcParams = {\n baseUrl,\n id,\n crop,\n hotspot,\n width,\n height,\n mode,\n queryParams,\n }\n\n const { src, ...outputDimensions } = buildSrc(srcParams)\n componentProps.srcSet = buildSrcSet(srcParams).join(\", \")\n componentProps.src = src\n componentProps.width = htmlWidth ?? outputDimensions.width\n componentProps.height = htmlHeight ?? outputDimensions.height\n\n if (preview) {\n componentProps.as = component ?? \"img\"\n componentProps.preview = preview\n }\n\n return <ImageComponent {...componentProps} />\n}\n"],
|
|
5
|
-
"mappings": "AAEO,IAAMA,EAA0B,qCAQ1BC,EAAgBC,GAA6B,CACxD,IAAMC,EAAQH,EAAwB,KAAKE,CAAE,EACvC,CAAC,CAAEE,EAASC,EAAYC,CAAM,EAAIH,GAAS,CAAC,EAElD,GAAI,CAACA,GAAS,CAACC,GAAW,CAACC,GAAc,CAACC,EACxC,MAAM,IAAI,MAAM,6BAA6BJ,IAAK,EAGpD,GAAM,CAACK,EAAOC,CAAM,EAAIH,EACrB,MAAM,GAAG,EACT,IAAKI,GAA0B,OAAO,SAASA,EAAO,EAAE,CAAC,EAE5D,GAAI,OAAO,MAAMF,CAAK,GAAK,OAAO,MAAMC,CAAM,GAAK,CAACD,GAAS,CAACC,EAC5D,MAAM,IAAI,MAAM,uBAAuBH,IAAa,EAGtD,MAAO,CACL,QAAAD,EACA,WAAY,CAAE,OAAAI,EAAQ,MAAAD,EAAO,YAAaA,EAAQC,CAAO,EACzD,OAAAF,CACF,CACF,EAUaI,EAAoBR,GAAuB,CAItD,IAAMS,EAAuBT,EAAG,YAAY,GAAG,EAE/C,OACEA,EAAG,MAAM,EAAGS,CAAoB,EAAI,IAAMT,EAAG,MAAMS,EAAuB,CAAC,CAE/E,ECrCO,IAAMC,EAAW,CAAC,CACvB,QAAAC,EACA,GAAGC,CACL,IAAyC,CACvC,GAAM,CAAE,SAAAC,EAAU,GAAGC,CAAY,EAAIC,EAAiB,CACpD,GAAGH,EACH,QAAS,CAAE,gBAAiB,EAAK,CACnC,CAAC,EAGD,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,+BAA+B,EAKjD,MAAO,CACL,IAAK,GAHU,GAAGF,IAAUK,EAAiBJ,EAAY,EAAE,OAGvCK,EAAiBH,CAAW,IAChD,MAAOD,EAAS,iBAAiB,MACjC,OAAQA,EAAS,iBAAiB,MACpC,CACF,EAEaK,EAAc,CAAC,CAC1B,GAAAC,EACA,KAAAC,EAAO,UACP,MAAAC,EACA,OAAAC,EACA,QAAAC,EACA,KAAAC,EACA,QAAAb,CACF,
|
|
6
|
-
"names": ["SANITY_IMAGE_ID_PATTERN", "parseImageId", "id", "match", "assetId", "dimensions", "format", "width", "height", "value", "imageIdToUrlPath", "formatSeparatorIndex", "buildSrc", "baseUrl", "inputParams", "metadata", "queryParams", "buildQueryParams", "imageIdToUrlPath", "buildQueryString", "buildSrcSet", "id", "mode", "width", "height", "hotspot", "crop", "w", "h", "imageUrl", "srcSetEntries", "dynamicMultipliers", "multiple", "computedWidth", "computedHeight", "params", "buildSvgAttributes", "assetId", "dimensions", "format", "parseImageId", "includeMetadata", "sourceDimensions", "maxWidth", "maxHeight", "sourceAspectRatio", "croppedImageSize", "requestedAspectRatio", "buildRect", "x", "y", "roundWithPrecision", "clamp", "outputHeight", "value", "min", "max", "precision", "aspectRatio", "a", "b", "key", "React", "useEffect", "useRef", "useState", "ImageWithPreview", "as", "preview", "props", "loaded", "setLoaded", "ref", "onLoad", "Img", "React", "SanityImage", "component", "baseUrl", "projectId", "dataset", "id", "hotspot", "crop", "width", "height", "mode", "preview", "htmlWidth", "htmlHeight", "htmlId", "queryParams", "rest", "isSvg", "ImageComponent", "ImageWithPreview", "componentProps", "React", "buildSvgAttributes", "srcParams", "src", "outputDimensions", "buildSrc", "buildSrcSet"]
|
|
4
|
+
"sourcesContent": ["import type { ImageIdParts } from \"./types\"\n\nexport const SANITY_IMAGE_ID_PATTERN = /^image-([\\da-f]+)-(\\d+x\\d+)-(\\w+)$/\n\n/**\n * Parse an image id string into its component parts.\n *\n * @param {string} id The image id string to parse in the format `image-<hash>-<width>x<height>.<ext>`\n * @returns {ImageIdParts} An object containing the asset ID, dimensions, and format\n */\nexport const parseImageId = (id: string): ImageIdParts => {\n const match = SANITY_IMAGE_ID_PATTERN.exec(id)\n const [, assetId, dimensions, format] = match ?? []\n\n if (!match || !assetId || !dimensions || !format) {\n throw new Error(`Could not parse image ID \"${id}\"`)\n }\n\n const [width, height] = dimensions\n .split(\"x\")\n .map((value: string): number => Number.parseInt(value, 10))\n\n if (Number.isNaN(width) || Number.isNaN(height) || !width || !height) {\n throw new Error(`Invalid dimensions \"${dimensions}\"`)\n }\n\n return {\n assetId,\n dimensions: { height, width, aspectRatio: width / height },\n format,\n }\n}\n\n/**\n * Convert an image id to a URL path segment for the Sanity Image API. Input is\n * not validated.\n *\n * @example\n * imageIdToUrlPath(\"image-<hash>-<width>x<height>-<ext>\")\n * // => \"<hash>-<width>x<height>.<ext>\"\n */\nexport const imageIdToUrlPath = (id: string): string => {\n // This can be implemented with `parseImageId` but it's more computationally expensive\n // than this more naive implementation.\n\n const formatSeparatorIndex = id.lastIndexOf(\"-\")\n\n return (\n id.slice(6, formatSeparatorIndex) + \".\" + id.slice(formatSeparatorIndex + 1)\n )\n}\n", "import { imageIdToUrlPath, parseImageId } from \"./parseImageId\"\nimport type {\n ComputedImageData,\n CropData,\n ImageIdParts,\n ImageQueryInputs,\n ImageQueryParams,\n ImageSrcInputs,\n} from \"./types\"\n\n/**\n * Convert ImageSrcInputs into a full image URL and computed output dimensions.\n */\nexport const buildSrc = ({\n baseUrl,\n ...inputParams\n}: ImageSrcInputs): ComputedImageData => {\n const { metadata, ...queryParams } = buildQueryParams({\n ...inputParams,\n options: { includeMetadata: true },\n })\n\n // Narrowing for TS\n if (!metadata) {\n throw new Error(\"Missing image output metadata\")\n }\n\n const imageUrl = `${baseUrl}${imageIdToUrlPath(inputParams.id)}`\n\n return {\n src: `${imageUrl}?${buildQueryString(queryParams)}`,\n width: metadata.outputDimensions.width,\n height: metadata.outputDimensions.height,\n }\n}\n\nexport const buildSrcSet = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n baseUrl,\n}: ImageSrcInputs): string[] => {\n // Determine base computed width\n const { w, h } = buildQueryParams({ id, mode, width, height, hotspot, crop })\n\n // URL of the image without any query parameters\n const imageUrl = `${baseUrl}${imageIdToUrlPath(id)}`\n\n // Build srcset\n const srcSetEntries: string[] = dynamicMultipliers(w)\n .map((multiple) => {\n const computedWidth = Math.round(w * multiple)\n const computedHeight = h && Math.round(h * multiple)\n\n // Ignore tiny entries; the extra data in the HTML is almost never worth it\n if (multiple < 1 && computedWidth < 50) return null\n\n const params: Omit<ImageQueryParams, \"metadata\"> = buildQueryParams({\n id,\n mode,\n width: computedWidth,\n height: computedHeight,\n hotspot,\n crop,\n })\n\n return `${imageUrl}?${buildQueryString(params)} ${params.w}w`\n })\n .filter((entry): entry is string => Boolean(entry))\n\n return Array.from(new Set(srcSetEntries))\n}\n\nexport const buildSvgAttributes = ({ id, baseUrl }: ImageSrcInputs) => {\n const { assetId, dimensions, format } = parseImageId(id)\n\n return {\n src: `${baseUrl}${assetId}-${dimensions.width}x${dimensions.height}.${format}`,\n width: dimensions.width,\n height: dimensions.height,\n }\n}\n\nconst dynamicMultipliers = (width: number): number[] => {\n // For really small images, use larger steps\n if (width < 160) {\n return [0.5, 1, 2]\n }\n\n // For typical width images, use standard steps\n if (width < 750) {\n return [0.5, 1, 1.5, 2]\n }\n\n // For larger images, include 0.25x and 0.75x steps\n if (width < 1400) {\n return [0.25, 0.5, 0.75, 1, 1.5, 2]\n }\n\n // For really large images, use a wider range of steps at the low end, and smaller steps at the high end\n return [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]\n}\n\n/**\n * Constructs a query parameters object for the Sanity image URL based on the inputs provided.\n */\nexport const buildQueryParams = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n queryParams,\n options: { includeMetadata = false } = {},\n}: ImageQueryInputs & {\n options?: {\n /** Include data about the image in the response */\n includeMetadata?: boolean\n }\n}): ImageQueryParams => {\n const sourceDimensions = parseImageId(id).dimensions\n\n // If crop is provided, compute post-crop dimensions\n const {\n width: maxWidth,\n height: maxHeight,\n aspectRatio: sourceAspectRatio,\n } = crop ? croppedImageSize(sourceDimensions, crop) : sourceDimensions\n\n // Determine width if not provided\n if (!width) {\n if (height) {\n // Compute width based on height and default ratio\n width = Math.round(height * sourceAspectRatio)\n\n // Discard `height` since we have to be in `contain` mode and we've converted it into `width`\n height = undefined\n } else {\n // Use 1/2 of the max image width by default to allow for 2x scale-up\n width = Math.round(maxWidth / 2)\n }\n }\n\n // Override `cover` mode if both width and height haven't been provided, or if\n // the requested aspect ratio matches the source aspect ratio. In these cases\n // the result will be the same as `contain` mode anyways, and `contain` mode\n // is simpler and saves a few bytes in the URL.\n if (\n mode === \"cover\" &&\n (!width || !height || width / height === sourceAspectRatio)\n ) {\n mode = \"contain\"\n } else if (mode === \"contain\" && height) {\n // Similarly, if `contain` mode is used and a height is provided, we can\n // convert it into a width by adjusting the width such that the\n // aspect-ratio\u2013constrained result will respect the height provided.\n width = Math.min(width, Math.round(height * sourceAspectRatio))\n height = undefined\n }\n\n // Clamp min and max dimensions while preserving requested aspect ratio\n if (width > maxWidth || (height && height > maxHeight)) {\n const requestedAspectRatio = height ? width / height : sourceAspectRatio\n\n if (requestedAspectRatio >= sourceAspectRatio) {\n // Clamp width\n width = maxWidth\n height = height && Math.round(width / requestedAspectRatio)\n } else {\n // Clamp height\n height = maxHeight\n width = Math.round(height * requestedAspectRatio)\n }\n }\n\n // Note: when converting params to a query string initially, we need to\n // use an object or map instead of URLSearchParams, since the latter will\n // allow multiple params with the same name, which is not supported by the\n // Sanity Image API.\n const params: Partial<ImageQueryParams> = {\n w: width,\n q: 75,\n ...queryParams,\n }\n\n // If an explicit format has not been requested, use auto format\n if (!params.fm) params.auto = \"format\"\n\n if (crop) {\n // Convert crop to rect param)\n params.rect = buildRect(sourceDimensions, crop)\n }\n\n if (mode === \"cover\") {\n params.fit = \"crop\"\n\n if (height) {\n params.h = height\n }\n\n if (hotspot) {\n // Hotspot is relative to post-`rect` dimensions; if `crop` is present,\n // the hotspot inputs need to be adjusted accordingly\n const x = crop ? hotspot.x / (1 - crop.left - crop.right) : hotspot.x\n const y = crop ? hotspot.y / (1 - crop.top - crop.bottom) : hotspot.y\n\n params[\"fp-x\"] = roundWithPrecision(clamp(x, 0, 1), 3)\n params[\"fp-y\"] = roundWithPrecision(clamp(y, 0, 1), 3)\n } else {\n // If no hotspot is provided, use Sanity\u2019s `entropy` crop mode\n params.crop = \"entropy\"\n }\n } else {\n params.fit = \"max\"\n }\n\n if (includeMetadata) {\n // Height will be set if the aspect ratio varies from `sourceAspectRatio`\n const outputHeight = height || Math.round(width / sourceAspectRatio)\n\n params.metadata = <ImageQueryParams[\"metadata\"]>{\n sourceDimensions,\n outputDimensions: {\n width,\n height: outputHeight,\n aspectRatio: width / outputHeight,\n },\n }\n }\n\n return <ImageQueryParams>params\n}\n\nconst clamp = (value: number, min: number, max: number): number =>\n Math.max(min, Math.min(max, value))\n\nconst roundWithPrecision = (value: number, precision: number): number =>\n Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision)\n\nexport const croppedImageSize = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n): ImageIdParts[\"dimensions\"] => {\n if (crop.left + crop.right >= 1 || crop.top + crop.bottom >= 1) {\n throw new Error(\n `Invalid crop: ${JSON.stringify(crop)}; crop values must be less than 1`\n )\n }\n\n const width = Math.round(dimensions.width * (1 - crop.left - crop.right))\n const height = Math.round(dimensions.height * (1 - crop.top - crop.bottom))\n const aspectRatio = width / height\n\n return { width, height, aspectRatio }\n}\n\n/**\n * Build a `rect` value to crop the image.\n */\nexport const buildRect = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n): string => {\n const { width, height } = croppedImageSize(dimensions, crop)\n\n return [\n Math.round(crop.left * dimensions.width),\n Math.round(crop.top * dimensions.height),\n width,\n height,\n ].join(\",\")\n}\n\n/**\n * Converts an object of query params into a query string. The keys are sorted\n * alphabetically to maximize cache-hit rates. Commas are not URL-encoded since\n * doing so is unnecessary, adds extra data, and makes it harder to read.\n */\nexport const buildQueryString = (\n params: Partial<{\n [K in keyof Omit<ImageQueryParams, \"metadata\">]: ImageQueryParams[K]\n }>\n): string => {\n const searchParams = new URLSearchParams(\n Object.entries(params)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, value]) => [key, String(value)])\n )\n\n return searchParams.toString().replace(/%2C/g, \",\") // don't urlencode commas\n}\n", "import React, { useEffect, useRef, useState } from \"react\"\nimport type { ImageWithPreviewProps } from \"./types\"\n\n/**\n * Renders two image tags, one with the preview image and one with the full\n * image. When the full image is loaded, the preview image is removed, revealing\n * the full image.\n */\nexport const ImageWithPreview = <T extends React.ElementType = \"img\">({\n as,\n preview,\n ...props\n}: ImageWithPreviewProps<T>) => {\n const [loaded, setLoaded] = useState(false)\n const ref = useRef<HTMLImageElement>(null)\n\n const onLoad = () => {\n setLoaded(true)\n }\n\n useEffect(() => {\n if (ref.current?.complete) {\n onLoad()\n }\n })\n\n const Img = as || \"img\"\n\n return (\n <>\n {!loaded && (\n <Img\n alt={props.alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={props.style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? undefined\n : {\n // must be > 4px to be lazy loaded\n height: \"10px !important\",\n\n // must be > 4px to be lazy loaded\n opacity: 0,\n\n pointerEvents: \"none\",\n // Cannot use negative x or y values, visibility: hidden, or display: none\n // to hide or the image might not get loaded.\n position: \"absolute\",\n userSelect: \"none\",\n width: \"10px !important\",\n zIndex: -10,\n }\n }\n {...props}\n />\n </>\n )\n}\n", "import React, {\n type ReactElement,\n type ElementType,\n type ComponentPropsWithoutRef,\n} from \"react\"\nimport type { PolymorphicComponentProps, SanityImageProps } from \"./types\"\nimport { buildSrc, buildSrcSet, buildSvgAttributes } from \"./urlBuilder\"\nimport { ImageWithPreview } from \"./ImageWithPreview\"\n\nexport const SanityImage = <C extends ElementType = \"img\">({\n as: component,\n\n // Sanity url\n baseUrl,\n projectId,\n dataset,\n\n // Image definition data\n id,\n hotspot,\n crop,\n width,\n height,\n mode = \"contain\",\n\n // Data for LQIP (preview image)\n preview,\n\n // Native-behavior overrides\n htmlWidth,\n htmlHeight,\n htmlId,\n\n // Image query string params\n queryParams,\n\n // Any remaining props are passed through to the rendered component\n ...rest\n}: PolymorphicComponentProps<C, SanityImageProps>): ReactElement => {\n if (!id) throw new Error(\"Missing required `id` prop for <SanityImage>.\")\n if (!baseUrl && (!projectId || !dataset))\n throw new Error(\n \"Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.\"\n )\n\n baseUrl = baseUrl ?? `https://cdn.sanity.io/images/${projectId}/${dataset}/`\n\n const isSvg = id.endsWith(\"-svg\")\n\n const ImageComponent =\n preview && !isSvg ? ImageWithPreview : component ?? \"img\"\n\n const componentProps: ComponentPropsWithoutRef<typeof ImageComponent> = {\n alt: rest.alt ?? \"\",\n loading: rest.loading ?? \"lazy\",\n id: htmlId,\n ...rest,\n }\n\n if (isSvg) {\n // Sanity ignores all transformations for SVGs, so we can just render the\n // component without passing a query string and without doing anything for\n // the preview.\n return (\n <ImageComponent\n {...buildSvgAttributes({ id, baseUrl })}\n {...componentProps}\n />\n )\n }\n\n // Create default src and build srcSet\n const srcParams = {\n baseUrl,\n id,\n crop,\n hotspot,\n width,\n height,\n mode,\n queryParams,\n }\n\n const { src, ...outputDimensions } = buildSrc(srcParams)\n componentProps.srcSet = buildSrcSet(srcParams).join(\", \")\n componentProps.src = src\n componentProps.width = htmlWidth ?? outputDimensions.width\n componentProps.height = htmlHeight ?? outputDimensions.height\n\n if (preview) {\n componentProps.as = component ?? \"img\"\n componentProps.preview = preview\n }\n\n return <ImageComponent {...componentProps} />\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["SANITY_IMAGE_ID_PATTERN", "parseImageId", "id", "match", "assetId", "dimensions", "format", "width", "height", "value", "imageIdToUrlPath", "formatSeparatorIndex", "buildSrc", "baseUrl", "inputParams", "metadata", "queryParams", "buildQueryParams", "imageIdToUrlPath", "buildQueryString", "buildSrcSet", "id", "mode", "width", "height", "hotspot", "crop", "w", "h", "imageUrl", "srcSetEntries", "dynamicMultipliers", "multiple", "computedWidth", "computedHeight", "params", "entry", "buildSvgAttributes", "assetId", "dimensions", "format", "parseImageId", "includeMetadata", "sourceDimensions", "maxWidth", "maxHeight", "sourceAspectRatio", "croppedImageSize", "requestedAspectRatio", "buildRect", "x", "y", "roundWithPrecision", "clamp", "outputHeight", "value", "min", "max", "precision", "aspectRatio", "a", "b", "key", "React", "useEffect", "useRef", "useState", "ImageWithPreview", "as", "preview", "props", "loaded", "setLoaded", "ref", "onLoad", "Img", "React", "SanityImage", "component", "baseUrl", "projectId", "dataset", "id", "hotspot", "crop", "width", "height", "mode", "preview", "htmlWidth", "htmlHeight", "htmlId", "queryParams", "rest", "isSvg", "ImageComponent", "ImageWithPreview", "componentProps", "React", "buildSvgAttributes", "srcParams", "src", "outputDimensions", "buildSrc", "buildSrcSet"]
|
|
7
7
|
}
|
package/dist/mjs/package.json
CHANGED
package/dist/urlBuilder.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { ComputedImageData, CropData, ImageIdParts, ImageQueryInputs, ImageQueryParams, ImageSrcInputs } from "types";
|
|
1
|
+
import type { ComputedImageData, CropData, ImageIdParts, ImageQueryInputs, ImageQueryParams, ImageSrcInputs } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* Convert ImageSrcInputs into a full image URL and computed output dimensions.
|
|
4
4
|
*/
|
|
5
5
|
export declare const buildSrc: ({ baseUrl, ...inputParams }: ImageSrcInputs) => ComputedImageData;
|
|
6
|
-
export declare const buildSrcSet: ({ id, mode, width, height, hotspot, crop, baseUrl, }: ImageSrcInputs) =>
|
|
6
|
+
export declare const buildSrcSet: ({ id, mode, width, height, hotspot, crop, baseUrl, }: ImageSrcInputs) => string[];
|
|
7
7
|
export declare const buildSvgAttributes: ({ id, baseUrl }: ImageSrcInputs) => {
|
|
8
8
|
src: string;
|
|
9
9
|
width: number;
|
|
@@ -29,4 +29,24 @@ export declare const buildRect: (dimensions: {
|
|
|
29
29
|
width: number;
|
|
30
30
|
height: number;
|
|
31
31
|
}, crop: CropData) => string;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Converts an object of query params into a query string. The keys are sorted
|
|
34
|
+
* alphabetically to maximize cache-hit rates. Commas are not URL-encoded since
|
|
35
|
+
* doing so is unnecessary, adds extra data, and makes it harder to read.
|
|
36
|
+
*/
|
|
37
|
+
export declare const buildQueryString: (params: Partial<{
|
|
38
|
+
auto: "format";
|
|
39
|
+
q: number;
|
|
40
|
+
rect?: string | undefined;
|
|
41
|
+
blur?: number | undefined;
|
|
42
|
+
h?: number | undefined;
|
|
43
|
+
crop?: "entropy" | undefined;
|
|
44
|
+
flip?: "h" | "v" | "hv" | undefined;
|
|
45
|
+
fm?: "jpg" | "pjpg" | "png" | "webp" | undefined;
|
|
46
|
+
sat?: -100 | undefined;
|
|
47
|
+
sharpen?: number | undefined;
|
|
48
|
+
fit: "max" | "crop";
|
|
49
|
+
"fp-x"?: number | undefined;
|
|
50
|
+
"fp-y"?: number | undefined;
|
|
51
|
+
w: number;
|
|
52
|
+
}>) => string;
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-image",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"author": "Corey Ward <corey@hey.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": "https://github.com/coreyward/sanity-image",
|
|
6
7
|
"main": "dist/cjs/index.js",
|
|
7
8
|
"module": "dist/mjs/index.js",
|
|
8
9
|
"types": "dist/index.d.ts",
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
"files": [
|
|
16
17
|
"/dist"
|
|
17
18
|
],
|
|
19
|
+
"sideEffects": false,
|
|
18
20
|
"peerDependencies": {
|
|
19
21
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
20
22
|
},
|
|
@@ -50,9 +52,9 @@
|
|
|
50
52
|
"react-test-renderer": "^18.2.0",
|
|
51
53
|
"rimraf": "^4.1.2",
|
|
52
54
|
"size-limit": "^8.2.4",
|
|
53
|
-
"ts-jest": "^29.0
|
|
55
|
+
"ts-jest": "^29.1.0",
|
|
54
56
|
"ts-node": "^10.9.1",
|
|
55
|
-
"typescript": "^
|
|
57
|
+
"typescript": "^5.0.3"
|
|
56
58
|
},
|
|
57
59
|
"scripts": {
|
|
58
60
|
"build": "rimraf dist && node ./scripts/build.js && tsc -p tsconfig.build.json",
|