sanity-image 0.2.0 → 1.0.0-alpha.0

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 CHANGED
@@ -225,23 +225,51 @@ userselect: none;
225
225
 
226
226
  I recommend creating a wrapper component internally to pass your `baseUrl` prop
227
227
  and pass through any props. This keeps the configuration in one place and gives
228
- you an entry point to add any other logic you might need. Here's a TypeScript
229
- example (for JavaScript, just remove the type annotation after `props`):
228
+ you an entry point to add any other logic you might need. There's a
229
+ [full `ImageWrapper` example](https://github.com/coreyward/sanity-image/blob/main/examples/ImageWrapper.tsx)
230
+ in the examples folder including comments. Here's a simplified version of that
231
+ example for quick reference:
230
232
 
231
- ```tsx
232
- import { SanityImage } from "sanity-image"
233
+ ````tsx
234
+ import * as React from "react"
235
+ import { SanityImage, type SanityImageProps } from "sanity-image"
233
236
 
234
237
  const projectId = process.env.SANITY_PROJECT_ID
235
238
  const dataset = process.env.SANITY_DATASET
236
239
  const baseUrl = `https://cdn.sanity.io/images/${projectId}/${dataset}/`
237
240
 
238
- export const Image = (
239
- props: Omit<
240
- React.ComponentProps<typeof SanityImage>,
241
- "baseUrl" | "dataset" | "projectId"
242
- >
241
+ /**
242
+ * These props are set automatically in this wrapper component, so we don't want
243
+ * them to be passed by consuming code.
244
+ */
245
+ type ConfigurationProps = "baseUrl" | "dataset" | "projectId"
246
+
247
+ /**
248
+ * Set the specified configuration props to `never` so that they can't be passed
249
+ * by consuming code.
250
+ */
251
+ type ExcludeConfigurationProps = Partial<Record<ConfigurationProps, never>>
252
+
253
+ /**
254
+ * A wrapper around `SanityImage` that configures the `baseUrl` prop
255
+ * automatically.
256
+ *
257
+ * Simple usage:
258
+ * @example
259
+ * ```tsx
260
+ * <Image
261
+ * id={image._id}
262
+ * hotspot={...}
263
+ * crop={...}
264
+ * width={450} // anticipated display width of the image
265
+ * alt="Some alt text. Can be dynamic/computed."
266
+ * />
267
+ * ```
268
+ */
269
+ export const Image = <T extends React.ElementType = "img">(
270
+ props: SanityImageProps<T> & ExcludeConfigurationProps
243
271
  ) => <SanityImage baseUrl={baseUrl} {...props} />
244
- ```
272
+ ````
245
273
 
246
274
  ### Styling your images
247
275
 
@@ -5,4 +5,4 @@ import type { ImageWithPreviewProps } from "./types";
5
5
  * image. When the full image is loaded, the preview image is removed, revealing
6
6
  * the full image.
7
7
  */
8
- export declare const ImageWithPreview: <T extends React.ElementType<any> = "img">({ as, preview, style, alt, ...props }: ImageWithPreviewProps<T>) => JSX.Element;
8
+ export declare const ImageWithPreview: <T extends React.ElementType = "img">({ as, preview, style, alt, ...props }: ImageWithPreviewProps<T>) => React.JSX.Element;
@@ -1,3 +1,3 @@
1
- import React, { type ReactElement } from "react";
2
- import type { PolymorphicComponentProps, SanityImageProps } from "./types";
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;
1
+ import React, { type ElementType } from "react";
2
+ import type { SanityImageProps } from "./types";
3
+ export declare const SanityImage: <T extends ElementType = "img">({ as: component, baseUrl, projectId, dataset, id, hotspot, crop, width, height, mode, preview, htmlWidth, htmlHeight, htmlId, queryParams, ...rest }: SanityImageProps<T>) => React.JSX.Element;
@@ -0,0 +1,20 @@
1
+ type AssetLike = {
2
+ _id: string;
3
+ } | {
4
+ _ref: string;
5
+ };
6
+ /**
7
+ * Get the asset ID of a Sanity image asset whether it has an `_id` or `_ref`
8
+ * field.
9
+ */
10
+ export declare const assetId: (asset: AssetLike) => string;
11
+ /**
12
+ * Normalize an asset object to have an `_id` field. This is useful when you
13
+ * have an asset object with a `_ref` field and you need to convert it to an
14
+ * `_id` field. Or if you don't know which you have and you want to ensure you
15
+ * have an `_id` field.
16
+ */
17
+ export declare const normalizeAssetId: <T extends Record<string, unknown>>(asset: AssetLike & T) => Omit<T, "_ref"> & {
18
+ _id: string;
19
+ };
20
+ export {};
package/dist/cjs/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var _=Object.create;var S=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var J=(e,t)=>{for(var r in t)S(e,r,{get:t[r],enumerable:!0})},R=(e,t,r,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of k(t))!G.call(e,n)&&n!==r&&S(e,n,{get:()=>t[n],enumerable:!(a=K(t,n))||a.enumerable});return e};var T=(e,t,r)=>(r=e!=null?_(B(e)):{},R(t||!e||!e.__esModule?S(r,"default",{value:e,enumerable:!0}):r,e)),Y=e=>R(S({},"__esModule",{value:!0}),e);var Z={};J(Z,{ImageWithPreview:()=>b,SanityImage:()=>j,buildSrc:()=>h,buildSrcSet:()=>x,parseImageId:()=>P});module.exports=Y(Z);var F=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,P=e=>{let t=F.exec(e),[,r,a,n]=t!=null?t:[];if(!t||!r||!a||!n)throw new Error(`Could not parse image ID "${e}"`);let[o,m]=a.split("x").map(f=>Number.parseInt(f,10));if(Number.isNaN(o)||Number.isNaN(m)||!o||!m)throw new Error(`Invalid dimensions "${a}"`);return{assetId:r,dimensions:{height:m,width:o,aspectRatio:o/m},format:n}},w=e=>{let t=e.lastIndexOf("-");return e.slice(6,t)+"."+e.slice(t+1)};var h=({baseUrl:e,...t})=>{let{metadata:r,...a}=M({...t,options:{includeMetadata:!0}});if(!r)throw new Error("Missing image output metadata");return{src:`${`${e}${w(t.id)}`}?${O(a)}`,width:r.outputDimensions.width,height:r.outputDimensions.height}},x=({id:e,mode:t="contain",width:r,height:a,hotspot:n,crop:o,baseUrl:m,...f})=>{let{w:u,h:p}=M({id:e,mode:t,width:r,height:a,hotspot:n,crop:o}),g=`${m}${w(e)}`,c=H(u).map(s=>{let i=Math.round(u*s),y=p&&Math.round(p*s);if(s<1&&i<50)return null;let l=M({id:e,mode:t,width:i,height:y,hotspot:n,crop:o,...f});return`${g}?${O(l)} ${l.w}w`}).filter(s=>!!s);return Array.from(new Set(c))},q=({id:e,baseUrl:t})=>{let{assetId:r,dimensions:a,format:n}=P(e);return{src:`${t}${r}-${a.width}x${a.height}.${n}`,width:a.width,height:a.height}},H=e=>e<160?[.5,1,2]:e<750?[.5,1,1.5,2]:e<1400?[.25,.5,.75,1,1.5,2]:[.25,.5,.75,1,1.25,1.5,1.75,2],M=({id:e,mode:t="contain",width:r,height:a,hotspot:n,crop:o,queryParams:m,options:{includeMetadata:f=!1}={}})=>{let u=P(e).dimensions,{width:p,height:g,aspectRatio:c}=o?L(u,o):u;if(r||(a?(r=Math.round(a*c),a=void 0):r=Math.round(p/2)),t==="cover"&&(!r||!a||r/a===c)?t="contain":t==="contain"&&a&&(r=Math.min(r,Math.round(a*c)),a=void 0),r>p||a&&a>g){let i=a?r/a:c;i>=c?(r=p,a=a&&Math.round(r/i)):(a=g,r=Math.round(a*i))}let s={w:r,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 i=o?(n.x-o.left)/(1-o.left-o.right):n.x,y=o?(n.y-o.top)/(1-o.top-o.bottom):n.y;s["fp-x"]=W(A(i,0,1),3),s["fp-y"]=W(A(y,0,1),3)}else s.crop="entropy";else s.fit="max";if(f){let i=a||Math.round(r/c);s.metadata={sourceDimensions:u,outputDimensions:{width:r,height:i,aspectRatio:r/i}}}return s},A=(e,t,r)=>Math.max(t,Math.min(r,e)),W=(e,t)=>Math.round(e*Math.pow(10,t))/Math.pow(10,t),L=(e,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 r=Math.round(e.width*(1-t.left-t.right)),a=Math.round(e.height*(1-t.top-t.bottom)),n=r/a;return{width:r,height:a,aspectRatio:n}},V=(e,t)=>{let{width:r,height:a}=L(e,t);return[Math.round(t.left*e.width),Math.round(t.top*e.height),r,a].join(",")},O=e=>new URLSearchParams(Object.entries(e).sort(([r],[a])=>r.localeCompare(a)).map(([r,a])=>[r,String(a)])).toString().replace(/%2C/g,",");var d=T(require("react")),b=({as:e,preview:t,style:r,alt:a,...n})=>{let[o,m]=(0,d.useState)(!1),f=(0,d.useRef)(null),u=()=>{m(!0)};(0,d.useEffect)(()=>{var g;(g=f.current)!=null&&g.complete&&u()});let p=e||"img";return d.default.createElement(d.default.Fragment,null,!o&&d.default.createElement(p,{alt:o?"":a,className:n.className,"data-lqip":!0,height:n.height,id:n.id,src:t,style:r,width:n.width}),d.default.createElement(p,{"data-loading":o?null:!0,alt:o?a:"",onLoad:u,ref:f,style:o?r:{...X,...r},...n}))},X={height:"10px",width:"10px",position:"absolute",zIndex:-10,opacity:0,pointerEvents:"none",userSelect:"none"};var $=T(require("react"));var j=({as:e,baseUrl:t,projectId:r,dataset:a,id:n,hotspot:o,crop:m,width:f,height:u,mode:p="contain",preview:g,htmlWidth:c,htmlHeight:s,htmlId:i,queryParams:y,...l})=>{var N,Q;if(!n)throw new Error("Missing required `id` prop for <SanityImage>.");if(!t&&(!r||!a))throw new Error("Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.");t=t!=null?t:`https://cdn.sanity.io/images/${r}/${a}/`;let C=n.endsWith("-svg"),E=g&&!C?b:e!=null?e:"img",I={alt:(N=l.alt)!=null?N:"",loading:(Q=l.loading)!=null?Q:"lazy",id:i,...l};if(C)return $.default.createElement(E,{...q({id:n,baseUrl:t}),...I});let v={baseUrl:t,id:n,crop:m,hotspot:o,width:f,height:u,mode:p,queryParams:y},{src:z,...D}=h(v);return I.srcSet=x(v).join(", "),I.src=z,I.width=c!=null?c:D.width,I.height=s!=null?s:D.height,g&&(I.as=e!=null?e:"img",I.preview=g),$.default.createElement(E,{...I})};0&&(module.exports={ImageWithPreview,SanityImage,buildSrc,buildSrcSet,parseImageId});
1
+ "use strict";var K=Object.create;var P=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var F=(t,e)=>{for(var r in e)P(t,r,{get:e[r],enumerable:!0})},R=(t,e,r,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of H(e))!Y.call(t,n)&&n!==r&&P(t,n,{get:()=>e[n],enumerable:!(a=G(e,n))||a.enumerable});return t};var _=(t,e,r)=>(r=t!=null?K(J(t)):{},R(e||!t||!t.__esModule?P(r,"default",{value:t,enumerable:!0}):r,t)),V=t=>R(P({},"__esModule",{value:!0}),t);var et={};F(et,{ImageWithPreview:()=>w,SanityImage:()=>j,assetId:()=>T,buildSrc:()=>h,buildSrcSet:()=>b,normalizeAssetId:()=>z,parseImageId:()=>S});module.exports=V(et);var X=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,S=t=>{let e=X.exec(t),[,r,a,n]=e!=null?e:[];if(!e||!r||!a||!n)throw new Error(`Could not parse image ID "${t}"`);let[s,m]=a.split("x").map(f=>Number.parseInt(f,10));if(Number.isNaN(s)||Number.isNaN(m)||!s||!m)throw new Error(`Invalid dimensions "${a}"`);return{assetId:r,dimensions:{height:m,width:s,aspectRatio:s/m},format:n}},M=t=>{let e=t.lastIndexOf("-");return t.slice(6,e)+"."+t.slice(e+1)};var h=({baseUrl:t,...e})=>{let{metadata:r,...a}=$({...e,options:{includeMetadata:!0}});if(!r)throw new Error("Missing image output metadata");return{src:`${`${t}${M(e.id)}`}?${O(a)}`,width:r.outputDimensions.width,height:r.outputDimensions.height}},b=({id:t,mode:e="contain",width:r,height:a,hotspot:n,crop:s,baseUrl:m,...f})=>{let{w:u,h:c}=$({id:t,mode:e,width:r,height:a,hotspot:n,crop:s}),g=`${m}${M(t)}`,d=Z(u).map(o=>{let i=Math.round(u*o),y=c&&Math.round(c*o);if(o<1&&i<50)return null;let l=$({id:t,mode:e,width:i,height:y,hotspot:n,crop:s,...f});return`${g}?${O(l)} ${l.w}w`}).filter(o=>!!o);return Array.from(new Set(d))},k=({id:t,baseUrl:e})=>{let{assetId:r,dimensions:a,format:n}=S(t);return{src:`${e}${r}-${a.width}x${a.height}.${n}`,width:a.width,height:a.height}},Z=t=>t<160?[.5,1,2]:t<750?[.5,1,1.5,2]:t<1400?[.25,.5,.75,1,1.5,2]:[.25,.5,.75,1,1.25,1.5,1.75,2],$=({id:t,mode:e="contain",width:r,height:a,hotspot:n,crop:s,queryParams:m,options:{includeMetadata:f=!1}={}})=>{let u=S(t).dimensions,{width:c,height:g,aspectRatio:d}=s?q(u,s):u;if(r||(a?(r=Math.round(a*d),a=void 0):r=Math.round(c/2)),e==="cover"&&(!r||!a||r/a===d)?e="contain":e==="contain"&&a&&(r=Math.min(r,Math.round(a*d)),a=void 0),r>c||a&&a>g){let i=a?r/a:d;i>=d?(r=c,a=a&&Math.round(r/i)):(a=g,r=Math.round(a*i))}let o={w:r,q:75,...m};if(o.fm||(o.auto="format"),s&&(o.rect=U(u,s)),e==="cover")if(o.fit="crop",a&&(o.h=a),n){let i=s?(n.x-s.left)/(1-s.left-s.right):n.x,y=s?(n.y-s.top)/(1-s.top-s.bottom):n.y;o["fp-x"]=W(L(i,0,1),3),o["fp-y"]=W(L(y,0,1),3)}else o.crop="entropy";else o.fit="max";if(f){let i=a||Math.round(r/d);o.metadata={sourceDimensions:u,outputDimensions:{width:r,height:i,aspectRatio:r/i}}}return o},L=(t,e,r)=>Math.max(e,Math.min(r,t)),W=(t,e)=>Math.round(t*Math.pow(10,e))/Math.pow(10,e),q=(t,e)=>{if(e.left+e.right>=1||e.top+e.bottom>=1)throw new Error(`Invalid crop: ${JSON.stringify(e)}; crop values must be less than 1`);let r=Math.round(t.width*(1-e.left-e.right)),a=Math.round(t.height*(1-e.top-e.bottom)),n=r/a;return{width:r,height:a,aspectRatio:n}},U=(t,e)=>{let{width:r,height:a}=q(t,e);return[Math.round(e.left*t.width),Math.round(e.top*t.height),r,a].join(",")},O=t=>new URLSearchParams(Object.entries(t).sort(([r],[a])=>r.localeCompare(a)).map(([r,a])=>[r,String(a)])).toString().replace(/%2C/g,",");var p=_(require("react")),w=({as:t,preview:e,style:r,alt:a,...n})=>{let[s,m]=(0,p.useState)(!1),f=(0,p.useRef)(null),u=()=>{m(!0)};(0,p.useEffect)(()=>{var g;(g=f.current)!=null&&g.complete&&u()});let c=t||"img";return p.default.createElement(p.default.Fragment,null,!s&&p.default.createElement(c,{alt:s?"":a,className:n.className,"data-lqip":!0,height:n.height,id:n.id,src:e,style:r,width:n.width}),p.default.createElement(c,{"data-loading":s?null:!0,alt:s?a:"",onLoad:u,ref:f,style:s?r:{...tt,...r},...n}))},tt={height:"10px",width:"10px",position:"absolute",zIndex:-10,opacity:0,pointerEvents:"none",userSelect:"none"};var T=t=>"_id"in t?t._id:t._ref,z=t=>{let{_ref:e,...r}=t;return{...r,_id:T(t)}};var A=_(require("react"));var j=({as:t,baseUrl:e,projectId:r,dataset:a,id:n,hotspot:s,crop:m,width:f,height:u,mode:c="contain",preview:g,htmlWidth:d,htmlHeight:o,htmlId:i,queryParams:y,...l})=>{var N,Q;if(!n)throw new Error("Missing required `id` prop for <SanityImage>.");if(!e&&(!r||!a))throw new Error("Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.");e=e!=null?e:`https://cdn.sanity.io/images/${r}/${a}/`;let E=n.endsWith("-svg"),v=g&&!E?w:t!=null?t:"img",I={alt:(N=l.alt)!=null?N:"",loading:(Q=l.loading)!=null?Q:"lazy",id:i,...l};if(E){let x=k({id:n,baseUrl:e});return t==="source"&&(x.srcSet=x.src,delete x.src),A.default.createElement(v,{...x,...I})}let C={baseUrl:e,id:n,crop:m,hotspot:s,width:f,height:u,mode:c,queryParams:y},{src:B,...D}=h(C);return I.srcSet=b(C).join(", "),I.src=B,I.width=d!=null?d:D.width,I.height=o!=null?o:D.height,g&&(I.as=t!=null?t:"img",I.preview=g),A.default.createElement(v,{...I})};0&&(module.exports={ImageWithPreview,SanityImage,assetId,buildSrc,buildSrcSet,normalizeAssetId,parseImageId});
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 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 ...inputParams\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 ...inputParams,\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\n ? (hotspot.x - crop.left) / (1 - crop.left - crop.right)\n : hotspot.x\n const y = crop\n ? (hotspot.y - crop.top) / (1 - crop.top - crop.bottom)\n : 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 style,\n alt,\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={loaded ? \"\" : alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n alt={loaded ? alt : \"\"}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? style\n : {\n ...baseStyles,\n ...style,\n }\n }\n {...props}\n />\n </>\n )\n}\n\nconst baseStyles: React.CSSProperties = {\n // must be > 4px to be lazy loaded\n height: \"10px\",\n\n // must be > 4px to be lazy loaded\n width: \"10px\",\n\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 zIndex: -10,\n opacity: 0,\n\n // Disable pointer events and user select to prevent the image\n // from interfering with UI while it's loading/hidden.\n pointerEvents: \"none\",\n userSelect: \"none\",\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", "roundWithPrecision", "clamp", "outputHeight", "value", "min", "max", "precision", "aspectRatio", "a", "b", "key", "import_react", "ImageWithPreview", "as", "preview", "style", "alt", "props", "loaded", "setLoaded", "ref", "onLoad", "_a", "Img", "React", "baseStyles", "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"]
3
+ "sources": ["../../src/index.ts", "../../src/parseImageId.ts", "../../src/urlBuilder.ts", "../../src/ImageWithPreview.tsx", "../../src/assetId.ts", "../../src/SanityImage.tsx"],
4
+ "sourcesContent": ["export type {\n Asset,\n HotspotData,\n CropData,\n SanityImageBaseProps,\n SanityImageProps,\n} from \"./types\"\nexport { buildSrc, buildSrcSet } from \"./urlBuilder\"\nexport { ImageWithPreview } from \"./ImageWithPreview\"\nexport { parseImageId } from \"./parseImageId\"\nexport { assetId, normalizeAssetId } from \"./assetId\"\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 ...inputParams\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 ...inputParams,\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\n ? (hotspot.x - crop.left) / (1 - crop.left - crop.right)\n : hotspot.x\n const y = crop\n ? (hotspot.y - crop.top) / (1 - crop.top - crop.bottom)\n : 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 style,\n alt,\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={loaded ? \"\" : alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n alt={loaded ? alt : \"\"}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? style\n : {\n ...baseStyles,\n ...style,\n }\n }\n {...props}\n />\n </>\n )\n}\n\nconst baseStyles: React.CSSProperties = {\n // must be > 4px to be lazy loaded\n height: \"10px\",\n\n // must be > 4px to be lazy loaded\n width: \"10px\",\n\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 zIndex: -10,\n opacity: 0,\n\n // Disable pointer events and user select to prevent the image\n // from interfering with UI while it's loading/hidden.\n pointerEvents: \"none\",\n userSelect: \"none\",\n}\n", "type AssetLike =\n | {\n _id: string\n }\n | {\n _ref: string\n }\n\n/**\n * Get the asset ID of a Sanity image asset whether it has an `_id` or `_ref`\n * field.\n */\nexport const assetId = (asset: AssetLike) =>\n \"_id\" in asset ? asset._id : asset._ref\n\n/**\n * Normalize an asset object to have an `_id` field. This is useful when you\n * have an asset object with a `_ref` field and you need to convert it to an\n * `_id` field. Or if you don't know which you have and you want to ensure you\n * have an `_id` field.\n */\nexport const normalizeAssetId = <T extends Record<string, unknown>>(\n asset: AssetLike & T\n): Omit<T, \"_ref\"> & { _id: string } => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { _ref, ...rest } = asset\n return { ...rest, _id: assetId(asset) }\n}\n", "import React, { type ElementType, type ComponentPropsWithoutRef } from \"react\"\nimport type { SanityImageProps } from \"./types\"\nimport { buildSrc, buildSrcSet, buildSvgAttributes } from \"./urlBuilder\"\nimport { ImageWithPreview } from \"./ImageWithPreview\"\n\nexport const SanityImage = <T 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}: SanityImageProps<T>) => {\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 const baseAttributes: Record<string, unknown> = buildSvgAttributes({\n id,\n baseUrl,\n })\n\n // If this is a <source> element, we need to set the `srcSet` attribute and not\n // the `src` attribute, otherwise it will be ignored in <picture> elements.\n if (component === \"source\") {\n baseAttributes.srcSet = baseAttributes.src\n delete baseAttributes.src\n }\n\n return <ImageComponent {...baseAttributes} {...componentProps} />\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": ["index_exports", "__export", "ImageWithPreview", "SanityImage", "assetId", "buildSrc", "buildSrcSet", "normalizeAssetId", "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", "roundWithPrecision", "clamp", "outputHeight", "value", "min", "max", "precision", "aspectRatio", "a", "b", "key", "import_react", "ImageWithPreview", "as", "preview", "style", "alt", "props", "loaded", "setLoaded", "ref", "onLoad", "_a", "Img", "React", "baseStyles", "assetId", "asset", "normalizeAssetId", "_ref", "rest", "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", "baseAttributes", "buildSvgAttributes", "React", "srcParams", "src", "outputDimensions", "buildSrc", "buildSrcSet"]
7
7
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ export type { Asset, HotspotData, CropData, SanityImageBaseProps, SanityImageProps, } from "./types";
1
2
  export { buildSrc, buildSrcSet } from "./urlBuilder";
2
3
  export { ImageWithPreview } from "./ImageWithPreview";
3
4
  export { parseImageId } from "./parseImageId";
5
+ export { assetId, normalizeAssetId } from "./assetId";
4
6
  export { SanityImage } from "./SanityImage";
package/dist/mjs/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var L=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,y=a=>{let t=L.exec(a),[,r,e,o]=t!=null?t:[];if(!t||!r||!e||!o)throw new Error(`Could not parse image ID "${a}"`);let[n,m]=e.split("x").map(d=>Number.parseInt(d,10));if(Number.isNaN(n)||Number.isNaN(m)||!n||!m)throw new Error(`Invalid dimensions "${e}"`);return{assetId:r,dimensions:{height:m,width:n,aspectRatio:n/m},format:o}},S=a=>{let t=a.lastIndexOf("-");return a.slice(6,t)+"."+a.slice(t+1)};var x=({baseUrl:a,...t})=>{let{metadata:r,...e}=h({...t,options:{includeMetadata:!0}});if(!r)throw new Error("Missing image output metadata");return{src:`${`${a}${S(t.id)}`}?${A(e)}`,width:r.outputDimensions.width,height:r.outputDimensions.height}},b=({id:a,mode:t="contain",width:r,height:e,hotspot:o,crop:n,baseUrl:m,...d})=>{let{w:u,h:p}=h({id:a,mode:t,width:r,height:e,hotspot:o,crop:n}),g=`${m}${S(a)}`,c=O(u).map(s=>{let i=Math.round(u*s),l=p&&Math.round(p*s);if(s<1&&i<50)return null;let I=h({id:a,mode:t,width:i,height:l,hotspot:o,crop:n,...d});return`${g}?${A(I)} ${I.w}w`}).filter(s=>!!s);return Array.from(new Set(c))},R=({id:a,baseUrl:t})=>{let{assetId:r,dimensions:e,format:o}=y(a);return{src:`${t}${r}-${e.width}x${e.height}.${o}`,width:e.width,height:e.height}},O=a=>a<160?[.5,1,2]:a<750?[.5,1,1.5,2]:a<1400?[.25,.5,.75,1,1.5,2]:[.25,.5,.75,1,1.25,1.5,1.75,2],h=({id:a,mode:t="contain",width:r,height:e,hotspot:o,crop:n,queryParams:m,options:{includeMetadata:d=!1}={}})=>{let u=y(a).dimensions,{width:p,height:g,aspectRatio:c}=n?T(u,n):u;if(r||(e?(r=Math.round(e*c),e=void 0):r=Math.round(p/2)),t==="cover"&&(!r||!e||r/e===c)?t="contain":t==="contain"&&e&&(r=Math.min(r,Math.round(e*c)),e=void 0),r>p||e&&e>g){let i=e?r/e:c;i>=c?(r=p,e=e&&Math.round(r/i)):(e=g,r=Math.round(e*i))}let s={w:r,q:75,...m};if(s.fm||(s.auto="format"),n&&(s.rect=j(u,n)),t==="cover")if(s.fit="crop",e&&(s.h=e),o){let i=n?(o.x-n.left)/(1-n.left-n.right):o.x,l=n?(o.y-n.top)/(1-n.top-n.bottom):o.y;s["fp-x"]=Q(N(i,0,1),3),s["fp-y"]=Q(N(l,0,1),3)}else s.crop="entropy";else s.fit="max";if(d){let i=e||Math.round(r/c);s.metadata={sourceDimensions:u,outputDimensions:{width:r,height:i,aspectRatio:r/i}}}return s},N=(a,t,r)=>Math.max(t,Math.min(r,a)),Q=(a,t)=>Math.round(a*Math.pow(10,t))/Math.pow(10,t),T=(a,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 r=Math.round(a.width*(1-t.left-t.right)),e=Math.round(a.height*(1-t.top-t.bottom)),o=r/e;return{width:r,height:e,aspectRatio:o}},j=(a,t)=>{let{width:r,height:e}=T(a,t);return[Math.round(t.left*a.width),Math.round(t.top*a.height),r,e].join(",")},A=a=>new URLSearchParams(Object.entries(a).sort(([r],[e])=>r.localeCompare(e)).map(([r,e])=>[r,String(e)])).toString().replace(/%2C/g,",");import P,{useEffect as z,useRef as _,useState as K}from"react";var w=({as:a,preview:t,style:r,alt:e,...o})=>{let[n,m]=K(!1),d=_(null),u=()=>{m(!0)};z(()=>{var g;(g=d.current)!=null&&g.complete&&u()});let p=a||"img";return P.createElement(P.Fragment,null,!n&&P.createElement(p,{alt:n?"":e,className:o.className,"data-lqip":!0,height:o.height,id:o.id,src:t,style:r,width:o.width}),P.createElement(p,{"data-loading":n?null:!0,alt:n?e:"",onLoad:u,ref:d,style:n?r:{...k,...r},...o}))},k={height:"10px",width:"10px",position:"absolute",zIndex:-10,opacity:0,pointerEvents:"none",userSelect:"none"};import W from"react";var B=({as:a,baseUrl:t,projectId:r,dataset:e,id:o,hotspot:n,crop:m,width:d,height:u,mode:p="contain",preview:g,htmlWidth:c,htmlHeight:s,htmlId:i,queryParams:l,...I})=>{var v,D;if(!o)throw new Error("Missing required `id` prop for <SanityImage>.");if(!t&&(!r||!e))throw new Error("Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.");t=t!=null?t:`https://cdn.sanity.io/images/${r}/${e}/`;let M=o.endsWith("-svg"),$=g&&!M?w:a!=null?a:"img",f={alt:(v=I.alt)!=null?v:"",loading:(D=I.loading)!=null?D:"lazy",id:i,...I};if(M)return W.createElement($,{...R({id:o,baseUrl:t}),...f});let C={baseUrl:t,id:o,crop:m,hotspot:n,width:d,height:u,mode:p,queryParams:l},{src:q,...E}=x(C);return f.srcSet=b(C).join(", "),f.src=q,f.width=c!=null?c:E.width,f.height=s!=null?s:E.height,g&&(f.as=a!=null?a:"img",f.preview=g),W.createElement($,{...f})};export{w as ImageWithPreview,B as SanityImage,x as buildSrc,b as buildSrcSet,y as parseImageId};
1
+ var q=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,S=e=>{let t=q.exec(e),[,r,a,s]=t!=null?t:[];if(!t||!r||!a||!s)throw new Error(`Could not parse image ID "${e}"`);let[n,m]=a.split("x").map(p=>Number.parseInt(p,10));if(Number.isNaN(n)||Number.isNaN(m)||!n||!m)throw new Error(`Invalid dimensions "${a}"`);return{assetId:r,dimensions:{height:m,width:n,aspectRatio:n/m},format:s}},P=e=>{let t=e.lastIndexOf("-");return e.slice(6,t)+"."+e.slice(t+1)};var b=({baseUrl:e,...t})=>{let{metadata:r,...a}=h({...t,options:{includeMetadata:!0}});if(!r)throw new Error("Missing image output metadata");return{src:`${`${e}${P(t.id)}`}?${_(a)}`,width:r.outputDimensions.width,height:r.outputDimensions.height}},w=({id:e,mode:t="contain",width:r,height:a,hotspot:s,crop:n,baseUrl:m,...p})=>{let{w:u,h:c}=h({id:e,mode:t,width:r,height:a,hotspot:s,crop:n}),g=`${m}${P(e)}`,d=O(u).map(o=>{let i=Math.round(u*o),l=c&&Math.round(c*o);if(o<1&&i<50)return null;let I=h({id:e,mode:t,width:i,height:l,hotspot:s,crop:n,...p});return`${g}?${_(I)} ${I.w}w`}).filter(o=>!!o);return Array.from(new Set(d))},Q=({id:e,baseUrl:t})=>{let{assetId:r,dimensions:a,format:s}=S(e);return{src:`${t}${r}-${a.width}x${a.height}.${s}`,width:a.width,height:a.height}},O=e=>e<160?[.5,1,2]:e<750?[.5,1,1.5,2]:e<1400?[.25,.5,.75,1,1.5,2]:[.25,.5,.75,1,1.25,1.5,1.75,2],h=({id:e,mode:t="contain",width:r,height:a,hotspot:s,crop:n,queryParams:m,options:{includeMetadata:p=!1}={}})=>{let u=S(e).dimensions,{width:c,height:g,aspectRatio:d}=n?R(u,n):u;if(r||(a?(r=Math.round(a*d),a=void 0):r=Math.round(c/2)),t==="cover"&&(!r||!a||r/a===d)?t="contain":t==="contain"&&a&&(r=Math.min(r,Math.round(a*d)),a=void 0),r>c||a&&a>g){let i=a?r/a:d;i>=d?(r=c,a=a&&Math.round(r/i)):(a=g,r=Math.round(a*i))}let o={w:r,q:75,...m};if(o.fm||(o.auto="format"),n&&(o.rect=z(u,n)),t==="cover")if(o.fit="crop",a&&(o.h=a),s){let i=n?(s.x-n.left)/(1-n.left-n.right):s.x,l=n?(s.y-n.top)/(1-n.top-n.bottom):s.y;o["fp-x"]=N(D(i,0,1),3),o["fp-y"]=N(D(l,0,1),3)}else o.crop="entropy";else o.fit="max";if(p){let i=a||Math.round(r/d);o.metadata={sourceDimensions:u,outputDimensions:{width:r,height:i,aspectRatio:r/i}}}return o},D=(e,t,r)=>Math.max(t,Math.min(r,e)),N=(e,t)=>Math.round(e*Math.pow(10,t))/Math.pow(10,t),R=(e,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 r=Math.round(e.width*(1-t.left-t.right)),a=Math.round(e.height*(1-t.top-t.bottom)),s=r/a;return{width:r,height:a,aspectRatio:s}},z=(e,t)=>{let{width:r,height:a}=R(e,t);return[Math.round(t.left*e.width),Math.round(t.top*e.height),r,a].join(",")},_=e=>new URLSearchParams(Object.entries(e).sort(([r],[a])=>r.localeCompare(a)).map(([r,a])=>[r,String(a)])).toString().replace(/%2C/g,",");import x,{useEffect as j,useRef as B,useState as K}from"react";var M=({as:e,preview:t,style:r,alt:a,...s})=>{let[n,m]=K(!1),p=B(null),u=()=>{m(!0)};j(()=>{var g;(g=p.current)!=null&&g.complete&&u()});let c=e||"img";return x.createElement(x.Fragment,null,!n&&x.createElement(c,{alt:n?"":a,className:s.className,"data-lqip":!0,height:s.height,id:s.id,src:t,style:r,width:s.width}),x.createElement(c,{"data-loading":n?null:!0,alt:n?a:"",onLoad:u,ref:p,style:n?r:{...G,...r},...s}))},G={height:"10px",width:"10px",position:"absolute",zIndex:-10,opacity:0,pointerEvents:"none",userSelect:"none"};var L=e=>"_id"in e?e._id:e._ref,H=e=>{let{_ref:t,...r}=e;return{...r,_id:L(e)}};import W from"react";var J=({as:e,baseUrl:t,projectId:r,dataset:a,id:s,hotspot:n,crop:m,width:p,height:u,mode:c="contain",preview:g,htmlWidth:d,htmlHeight:o,htmlId:i,queryParams:l,...I})=>{var v,C;if(!s)throw new Error("Missing required `id` prop for <SanityImage>.");if(!t&&(!r||!a))throw new Error("Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.");t=t!=null?t:`https://cdn.sanity.io/images/${r}/${a}/`;let $=s.endsWith("-svg"),T=g&&!$?M:e!=null?e:"img",f={alt:(v=I.alt)!=null?v:"",loading:(C=I.loading)!=null?C:"lazy",id:i,...I};if($){let y=Q({id:s,baseUrl:t});return e==="source"&&(y.srcSet=y.src,delete y.src),W.createElement(T,{...y,...f})}let A={baseUrl:t,id:s,crop:m,hotspot:n,width:p,height:u,mode:c,queryParams:l},{src:k,...E}=b(A);return f.srcSet=w(A).join(", "),f.src=k,f.width=d!=null?d:E.width,f.height=o!=null?o:E.height,g&&(f.as=e!=null?e:"img",f.preview=g),W.createElement(T,{...f})};export{M as ImageWithPreview,J as SanityImage,L as assetId,b as buildSrc,w as buildSrcSet,H as normalizeAssetId,S as parseImageId};
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 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 ...inputParams\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 ...inputParams,\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\n ? (hotspot.x - crop.left) / (1 - crop.left - crop.right)\n : hotspot.x\n const y = crop\n ? (hotspot.y - crop.top) / (1 - crop.top - crop.bottom)\n : 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 style,\n alt,\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={loaded ? \"\" : alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n alt={loaded ? alt : \"\"}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? style\n : {\n ...baseStyles,\n ...style,\n }\n }\n {...props}\n />\n </>\n )\n}\n\nconst baseStyles: React.CSSProperties = {\n // must be > 4px to be lazy loaded\n height: \"10px\",\n\n // must be > 4px to be lazy loaded\n width: \"10px\",\n\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 zIndex: -10,\n opacity: 0,\n\n // Disable pointer events and user select to prevent the image\n // from interfering with UI while it's loading/hidden.\n pointerEvents: \"none\",\n userSelect: \"none\",\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,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,EACA,GAAGC,CACL,IAAgC,CAE9B,GAAM,CAAE,EAAAa,EAAG,EAAAC,CAAE,EAAIX,EAAiB,CAAE,GAAAI,EAAI,KAAAC,EAAM,MAAAC,EAAO,OAAAC,EAAQ,QAAAC,EAAS,KAAAC,CAAK,CAAC,EAGtEG,EAAW,GAAGhB,IAAUK,EAAiBG,CAAE,IAG3CS,EAA0BC,EAAmBJ,CAAC,EACjD,IAAKK,GAAa,CACjB,IAAMC,EAAgB,KAAK,MAAMN,EAAIK,CAAQ,EACvCE,EAAiBN,GAAK,KAAK,MAAMA,EAAII,CAAQ,EAGnD,GAAIA,EAAW,GAAKC,EAAgB,GAAI,OAAO,KAE/C,IAAME,EAA6ClB,EAAiB,CAClE,GAAAI,EACA,KAAAC,EACA,MAAOW,EACP,OAAQC,EACR,QAAAT,EACA,KAAAC,EACA,GAAGZ,CACL,CAAC,EAED,MAAO,GAAGe,KAAYV,EAAiBgB,CAAM,KAAKA,EAAO,IAC3D,CAAC,EACA,OAAQC,GAA2B,EAAQA,CAAM,EAEpD,OAAO,MAAM,KAAK,IAAI,IAAIN,CAAa,CAAC,CAC1C,EAEaO,EAAqB,CAAC,CAAE,GAAAhB,EAAI,QAAAR,CAAQ,IAAsB,CACrE,GAAM,CAAE,QAAAyB,EAAS,WAAAC,EAAY,OAAAC,CAAO,EAAIC,EAAapB,CAAE,EAEvD,MAAO,CACL,IAAK,GAAGR,IAAUyB,KAAWC,EAAW,SAASA,EAAW,UAAUC,IACtE,MAAOD,EAAW,MAClB,OAAQA,EAAW,MACrB,CACF,EAEMR,EAAsBR,GAEtBA,EAAQ,IACH,CAAC,GAAK,EAAG,CAAC,EAIfA,EAAQ,IACH,CAAC,GAAK,EAAG,IAAK,CAAC,EAIpBA,EAAQ,KACH,CAAC,IAAM,GAAK,IAAM,EAAG,IAAK,CAAC,EAI7B,CAAC,IAAM,GAAK,IAAM,EAAG,KAAM,IAAK,KAAM,CAAC,EAMnCN,EAAmB,CAAC,CAC/B,GAAAI,EACA,KAAAC,EAAO,UACP,MAAAC,EACA,OAAAC,EACA,QAAAC,EACA,KAAAC,EACA,YAAAV,EACA,QAAS,CAAE,gBAAA0B,EAAkB,EAAM,EAAI,CAAC,CAC1C,IAKwB,CACtB,IAAMC,EAAmBF,EAAapB,CAAE,EAAE,WAGpC,CACJ,MAAOuB,EACP,OAAQC,EACR,YAAaC,CACf,EAAIpB,EAAOqB,EAAiBJ,EAAkBjB,CAAI,EAAIiB,EAkCtD,GA/BKpB,IACCC,GAEFD,EAAQ,KAAK,MAAMC,EAASsB,CAAiB,EAG7CtB,EAAS,QAGTD,EAAQ,KAAK,MAAMqB,EAAW,CAAC,GASjCtB,IAAS,UACR,CAACC,GAAS,CAACC,GAAUD,EAAQC,IAAWsB,GAEzCxB,EAAO,UACEA,IAAS,WAAaE,IAI/BD,EAAQ,KAAK,IAAIA,EAAO,KAAK,MAAMC,EAASsB,CAAiB,CAAC,EAC9DtB,EAAS,QAIPD,EAAQqB,GAAapB,GAAUA,EAASqB,EAAY,CACtD,IAAMG,EAAuBxB,EAASD,EAAQC,EAASsB,EAEnDE,GAAwBF,GAE1BvB,EAAQqB,EACRpB,EAASA,GAAU,KAAK,MAAMD,EAAQyB,CAAoB,IAG1DxB,EAASqB,EACTtB,EAAQ,KAAK,MAAMC,EAASwB,CAAoB,GAQpD,IAAMb,EAAoC,CACxC,EAAGZ,EACH,EAAG,GACH,GAAGP,CACL,EAUA,GAPKmB,EAAO,KAAIA,EAAO,KAAO,UAE1BT,IAEFS,EAAO,KAAOc,EAAUN,EAAkBjB,CAAI,GAG5CJ,IAAS,QAOX,GANAa,EAAO,IAAM,OAETX,IACFW,EAAO,EAAIX,GAGTC,EAAS,CAGX,IAAMyB,EAAIxB,GACLD,EAAQ,EAAIC,EAAK,OAAS,EAAIA,EAAK,KAAOA,EAAK,OAChDD,EAAQ,EACN0B,EAAIzB,GACLD,EAAQ,EAAIC,EAAK,MAAQ,EAAIA,EAAK,IAAMA,EAAK,QAC9CD,EAAQ,EAEZU,EAAO,MAAM,EAAIiB,EAAmBC,EAAMH,EAAG,EAAG,CAAC,EAAG,CAAC,EACrDf,EAAO,MAAM,EAAIiB,EAAmBC,EAAMF,EAAG,EAAG,CAAC,EAAG,CAAC,OAGrDhB,EAAO,KAAO,eAGhBA,EAAO,IAAM,MAGf,GAAIO,EAAiB,CAEnB,IAAMY,EAAe9B,GAAU,KAAK,MAAMD,EAAQuB,CAAiB,EAEnEX,EAAO,SAAyC,CAC9C,iBAAAQ,EACA,iBAAkB,CAChB,MAAApB,EACA,OAAQ+B,EACR,YAAa/B,EAAQ+B,CACvB,CACF,EAGF,OAAyBnB,CAC3B,EAEMkB,EAAQ,CAACE,EAAeC,EAAaC,IACzC,KAAK,IAAID,EAAK,KAAK,IAAIC,EAAKF,CAAK,CAAC,EAE9BH,EAAqB,CAACG,EAAeG,IACzC,KAAK,MAAMH,EAAQ,KAAK,IAAI,GAAIG,CAAS,CAAC,EAAI,KAAK,IAAI,GAAIA,CAAS,EAEzDX,EAAmB,CAE9BR,EACAb,IAC+B,CAC/B,GAAIA,EAAK,KAAOA,EAAK,OAAS,GAAKA,EAAK,IAAMA,EAAK,QAAU,EAC3D,MAAM,IAAI,MACR,iBAAiB,KAAK,UAAUA,CAAI,oCACtC,EAGF,IAAMH,EAAQ,KAAK,MAAMgB,EAAW,OAAS,EAAIb,EAAK,KAAOA,EAAK,MAAM,EAClEF,EAAS,KAAK,MAAMe,EAAW,QAAU,EAAIb,EAAK,IAAMA,EAAK,OAAO,EACpEiC,EAAcpC,EAAQC,EAE5B,MAAO,CAAE,MAAAD,EAAO,OAAAC,EAAQ,YAAAmC,CAAY,CACtC,EAKaV,EAAY,CAEvBV,EACAb,IACW,CACX,GAAM,CAAE,MAAAH,EAAO,OAAAC,CAAO,EAAIuB,EAAiBR,EAAYb,CAAI,EAE3D,MAAO,CACL,KAAK,MAAMA,EAAK,KAAOa,EAAW,KAAK,EACvC,KAAK,MAAMb,EAAK,IAAMa,EAAW,MAAM,EACvChB,EACAC,CACF,EAAE,KAAK,GAAG,CACZ,EAOaL,EACXgB,GAIqB,IAAI,gBACvB,OAAO,QAAQA,CAAM,EAClB,KAAK,CAAC,CAACyB,CAAC,EAAG,CAACC,CAAC,IAAMD,EAAE,cAAcC,CAAC,CAAC,EACrC,IAAI,CAAC,CAACC,EAAKP,CAAK,IAAM,CAACO,EAAK,OAAOP,CAAK,CAAC,CAAC,CAC/C,EAEoB,SAAS,EAAE,QAAQ,OAAQ,GAAG,EC7SpD,OAAOQ,GAAS,aAAAC,EAAW,UAAAC,EAAQ,YAAAC,MAAgB,QAQ5C,IAAMC,EAAmB,CAAsC,CACpE,GAAAC,EACA,QAAAC,EACA,MAAAC,EACA,IAAAC,EACA,GAAGC,CACL,IAAgC,CAC9B,GAAM,CAACC,EAAQC,CAAS,EAAIR,EAAS,EAAK,EACpCS,EAAMV,EAAyB,IAAI,EAEnCW,EAAS,IAAM,CACnBF,EAAU,EAAI,CAChB,EAEAV,EAAU,IAAM,CAtBlB,IAAAa,GAuBQA,EAAAF,EAAI,UAAJ,MAAAE,EAAa,UACfD,EAAO,CAEX,CAAC,EAED,IAAME,EAAMV,GAAM,MAElB,OACEL,EAAA,cAAAA,EAAA,cACG,CAACU,GACAV,EAAA,cAACe,EAAA,CACC,IAAKL,EAAS,GAAKF,EACnB,UAAWC,EAAM,UACjB,YAAS,GACT,OAAQA,EAAM,OACd,GAAIA,EAAM,GACV,IAAKH,EACL,MAAOC,EACP,MAAOE,EAAM,MACf,EAEFT,EAAA,cAACe,EAAA,CACC,eAAcL,EAAS,KAAO,GAC9B,IAAKA,EAASF,EAAM,GACpB,OAAQK,EACR,IAAKD,EACL,MACEF,EACIH,EACA,CACE,GAAGS,EACH,GAAGT,CACL,EAEL,GAAGE,EACN,CACF,CAEJ,EAEMO,EAAkC,CAEtC,OAAQ,OAGR,MAAO,OAIP,SAAU,WACV,OAAQ,IACR,QAAS,EAIT,cAAe,OACf,WAAY,MACd,EChFA,OAAOC,MAIA,QAKA,IAAMC,EAAc,CAAgC,CACzD,GAAIC,EAGJ,QAAAC,EACA,UAAAC,EACA,QAAAC,EAGA,GAAAC,EACA,QAAAC,EACA,KAAAC,EACA,MAAAC,EACA,OAAAC,EACA,KAAAC,EAAO,UAGP,QAAAC,EAGA,UAAAC,EACA,WAAAC,EACA,OAAAC,EAGA,YAAAC,EAGA,GAAGC,CACL,IAAoE,CAtCpE,IAAAC,EAAAC,EAuCE,GAAI,CAACb,EAAI,MAAM,IAAI,MAAM,+CAA+C,EACxE,GAAI,CAACH,IAAY,CAACC,GAAa,CAACC,GAC9B,MAAM,IAAI,MACR,kFACF,EAEFF,EAAUA,GAAA,KAAAA,EAAW,gCAAgCC,KAAaC,KAElE,IAAMe,EAAQd,EAAG,SAAS,MAAM,EAE1Be,EACJT,GAAW,CAACQ,EAAQE,EAAmBpB,GAAA,KAAAA,EAAa,MAEhDqB,EAAkE,CACtE,KAAKL,EAAAD,EAAK,MAAL,KAAAC,EAAY,GACjB,SAASC,EAAAF,EAAK,UAAL,KAAAE,EAAgB,OACzB,GAAIJ,EACJ,GAAGE,CACL,EAEA,GAAIG,EAIF,OACEI,EAAA,cAACH,EAAA,CACE,GAAGI,EAAmB,CAAE,GAAAnB,EAAI,QAAAH,CAAQ,CAAC,EACrC,GAAGoB,EACN,EAKJ,IAAMG,EAAY,CAChB,QAAAvB,EACA,GAAAG,EACA,KAAAE,EACA,QAAAD,EACA,MAAAE,EACA,OAAAC,EACA,KAAAC,EACA,YAAAK,CACF,EAEM,CAAE,IAAAW,EAAK,GAAGC,CAAiB,EAAIC,EAASH,CAAS,EACvD,OAAAH,EAAe,OAASO,EAAYJ,CAAS,EAAE,KAAK,IAAI,EACxDH,EAAe,IAAMI,EACrBJ,EAAe,MAAQV,GAAA,KAAAA,EAAae,EAAiB,MACrDL,EAAe,OAAST,GAAA,KAAAA,EAAcc,EAAiB,OAEnDhB,IACFW,EAAe,GAAKrB,GAAA,KAAAA,EAAa,MACjCqB,EAAe,QAAUX,GAGpBY,EAAA,cAACH,EAAA,CAAgB,GAAGE,EAAgB,CAC7C",
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", "style", "alt", "props", "loaded", "setLoaded", "ref", "onLoad", "_a", "Img", "baseStyles", "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"]
3
+ "sources": ["../../src/parseImageId.ts", "../../src/urlBuilder.ts", "../../src/ImageWithPreview.tsx", "../../src/assetId.ts", "../../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 ...inputParams\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 ...inputParams,\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\n ? (hotspot.x - crop.left) / (1 - crop.left - crop.right)\n : hotspot.x\n const y = crop\n ? (hotspot.y - crop.top) / (1 - crop.top - crop.bottom)\n : 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 style,\n alt,\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={loaded ? \"\" : alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n alt={loaded ? alt : \"\"}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? style\n : {\n ...baseStyles,\n ...style,\n }\n }\n {...props}\n />\n </>\n )\n}\n\nconst baseStyles: React.CSSProperties = {\n // must be > 4px to be lazy loaded\n height: \"10px\",\n\n // must be > 4px to be lazy loaded\n width: \"10px\",\n\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 zIndex: -10,\n opacity: 0,\n\n // Disable pointer events and user select to prevent the image\n // from interfering with UI while it's loading/hidden.\n pointerEvents: \"none\",\n userSelect: \"none\",\n}\n", "type AssetLike =\n | {\n _id: string\n }\n | {\n _ref: string\n }\n\n/**\n * Get the asset ID of a Sanity image asset whether it has an `_id` or `_ref`\n * field.\n */\nexport const assetId = (asset: AssetLike) =>\n \"_id\" in asset ? asset._id : asset._ref\n\n/**\n * Normalize an asset object to have an `_id` field. This is useful when you\n * have an asset object with a `_ref` field and you need to convert it to an\n * `_id` field. Or if you don't know which you have and you want to ensure you\n * have an `_id` field.\n */\nexport const normalizeAssetId = <T extends Record<string, unknown>>(\n asset: AssetLike & T\n): Omit<T, \"_ref\"> & { _id: string } => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { _ref, ...rest } = asset\n return { ...rest, _id: assetId(asset) }\n}\n", "import React, { type ElementType, type ComponentPropsWithoutRef } from \"react\"\nimport type { SanityImageProps } from \"./types\"\nimport { buildSrc, buildSrcSet, buildSvgAttributes } from \"./urlBuilder\"\nimport { ImageWithPreview } from \"./ImageWithPreview\"\n\nexport const SanityImage = <T 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}: SanityImageProps<T>) => {\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 const baseAttributes: Record<string, unknown> = buildSvgAttributes({\n id,\n baseUrl,\n })\n\n // If this is a <source> element, we need to set the `srcSet` attribute and not\n // the `src` attribute, otherwise it will be ignored in <picture> elements.\n if (component === \"source\") {\n baseAttributes.srcSet = baseAttributes.src\n delete baseAttributes.src\n }\n\n return <ImageComponent {...baseAttributes} {...componentProps} />\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", "style", "alt", "props", "loaded", "setLoaded", "ref", "onLoad", "_a", "Img", "baseStyles", "assetId", "asset", "normalizeAssetId", "_ref", "rest", "React", "SanityImage", "component", "baseUrl", "projectId", "dataset", "id", "hotspot", "crop", "width", "height", "mode", "preview", "htmlWidth", "htmlHeight", "htmlId", "queryParams", "rest", "_a", "_b", "isSvg", "ImageComponent", "ImageWithPreview", "componentProps", "baseAttributes", "buildSvgAttributes", "React", "srcParams", "src", "outputDimensions", "buildSrc", "buildSrcSet"]
7
7
  }
package/dist/types.d.ts CHANGED
@@ -1,39 +1,67 @@
1
- import type { AsProp } from "./polymorphic-voodoo";
2
- export type { AsProp, PropsOf, ExtendableProps, InheritableElementProps, PolymorphicComponentProps, } from "./polymorphic-voodoo";
3
- export type SanityImageProps = ImageQueryInputs & {
1
+ /**
2
+ * The `as` prop allows for overriding the default element type of the
3
+ * component.
4
+ */
5
+ export type AsProp<T extends React.ElementType> = {
6
+ /**
7
+ * By default, the component will render an `<img>` tag. You can override this
8
+ * by passing a different component or HTML tag name.
9
+ */
10
+ as?: T;
11
+ };
12
+ /**
13
+ * Allows for extending a set of props (`ExtendedProps`) by an overriding set of
14
+ * props (`OverrideProps`), ensuring that any duplicates are overridden by the
15
+ * overriding set of props.
16
+ */
17
+ export type ExtendableProps<ExtendedProps = object, OverrideProps = object> = OverrideProps & Omit<ExtendedProps, keyof OverrideProps>;
18
+ /**
19
+ * Allows for inheriting the props from the specified element type so that props
20
+ * like children, className & style work, as well as element-specific attributes
21
+ * like aria roles.
22
+ */
23
+ export type InheritableElementProps<T extends React.ElementType, Props = object> = ExtendableProps<React.ComponentPropsWithoutRef<T>, Props>;
24
+ /**
25
+ * A more sophisticated version of `InheritableElementProps` where the passed in
26
+ * `as` prop will determine which props can be included
27
+ */
28
+ export type PolymorphicComponentProps<T extends React.ElementType, Props = object> = InheritableElementProps<T, Props & AsProp<T>>;
29
+ /**
30
+ * Base props for SanityImage. This represents all Sanity-specific fields that
31
+ * can be provided without any of the React element attributes/props.
32
+ */
33
+ export type SanityImageBaseProps = ImageQueryInputs & {
4
34
  preview?: string;
5
35
  /**
6
- * The base url for the Sanity CDN including project ID and dataset. If not
7
- * provided, the `projectId` and `dataset` props will be used to construct the
8
- * URL.
36
+ * The base URL for the Sanity CDN. If not provided, the `projectId` and
37
+ * `dataset` props will be used to construct the URL.
9
38
  */
10
39
  baseUrl?: string;
11
40
  /**
12
- * The Sanity project ID to use for the image URL. If preferred, the `baseUrl`
13
- * prop can be provided instead.
41
+ * The Sanity project ID to use. If preferred, provide `baseUrl` instead.
14
42
  */
15
43
  projectId?: string;
16
44
  /**
17
- * The Sanity dataset to use for the image URL. If preferred, the `baseUrl`
18
- * prop can be provided instead.
45
+ * The Sanity dataset to use. If preferred, provide `baseUrl` instead.
19
46
  */
20
47
  dataset?: string;
21
48
  /**
22
- * `alt` attribute for the image; set to an empty string if not provided.
23
- */
24
- /**
25
- * Passed through to the <img> tag as `height`, overriding the `aspectRatio`
26
- * option, if enabled.
49
+ * Passed through to the rendered element as `height`, overriding the default
50
+ * behavior of setting the `height` property automatically based on the
51
+ * computed output image dimensions.
27
52
  */
28
53
  htmlHeight?: number;
29
54
  /**
30
- * Passed through to the <img> tag as `width`, overriding the `aspectRatio`
31
- * option, if enabled.
55
+ * Passed through to the rendered element as `width`, overriding the default
56
+ * behavior of setting the `width` property automatically based on the
57
+ * computed output image dimensions.
32
58
  */
33
59
  htmlWidth?: number;
34
60
  /**
35
- * Passed through to the <img> tag as `id` since the `id` prop is reserved for
36
- * the Sanity image asset ID.
61
+ * Passed through to the rendered element as `id`.
62
+ *
63
+ * The `id` prop is used to specify the Sanity Image ID, so this is the only
64
+ * way to set the `id` attribute on the rendered element.
37
65
  */
38
66
  htmlId?: string;
39
67
  /**
@@ -45,19 +73,34 @@ export type SanityImageProps = ImageQueryInputs & {
45
73
  */
46
74
  queryParams?: DirectQueryParams;
47
75
  };
76
+ /**
77
+ * Props type for the polymorphic <SanityImage> component.
78
+ */
79
+ export type SanityImageProps<T extends React.ElementType> = PolymorphicComponentProps<T, SanityImageBaseProps>;
48
80
  export type ImageWithPreviewProps<T extends React.ElementType> = {
49
81
  preview: string;
50
82
  } & AsProp<T> & React.ComponentPropsWithRef<T>;
83
+ /**
84
+ * Asset crop data. All values are required.
85
+ */
51
86
  export type CropData = {
52
87
  bottom: number;
53
88
  left: number;
54
89
  right: number;
55
90
  top: number;
56
91
  };
92
+ /**
93
+ * Asset hotspot data..
94
+ */
57
95
  export type HotspotData = {
58
96
  x: number;
59
97
  y: number;
60
98
  };
99
+ /**
100
+ * A Sanity asset. This type expects the `_id` field to be set, but in many
101
+ * cases you will have a `_ref` field instead. This is the same value and is
102
+ * safe to convert to `_id`.
103
+ */
61
104
  export type Asset = {
62
105
  _id: string;
63
106
  crop?: CropData;
@@ -15,8 +15,8 @@ export declare const buildSvgAttributes: ({ id, baseUrl }: ImageSrcInputs) => {
15
15
  export declare const buildQueryParams: ({ id, mode, width, height, hotspot, crop, queryParams, options: { includeMetadata }, }: ImageQueryInputs & {
16
16
  options?: {
17
17
  /** Include data about the image in the response */
18
- includeMetadata?: boolean | undefined;
19
- } | undefined;
18
+ includeMetadata?: boolean;
19
+ };
20
20
  }) => ImageQueryParams;
21
21
  export declare const croppedImageSize: (dimensions: {
22
22
  width: number;
@@ -34,19 +34,4 @@ export declare const buildRect: (dimensions: {
34
34
  * alphabetically to maximize cache-hit rates. Commas are not URL-encoded since
35
35
  * doing so is unnecessary, adds extra data, and makes it harder to read.
36
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;
37
+ export declare const buildQueryString: (params: Partial<{ [K in keyof Omit<ImageQueryParams, "metadata">]: ImageQueryParams[K]; }>) => string;
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "sanity-image",
3
- "version": "0.2.0",
3
+ "version": "1.0.0-alpha.0",
4
4
  "author": "Corey Ward <corey@hey.com>",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/coreyward/sanity-image",
7
+ "types": "dist/index.d.ts",
7
8
  "main": "dist/cjs/index.js",
8
9
  "module": "dist/mjs/index.js",
9
- "types": "dist/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
+ "types": "./dist/index.d.ts",
12
13
  "import": "./dist/mjs/index.js",
13
- "require": "./dist/cjs/index.js",
14
- "types": "./dist/index.d.ts"
14
+ "require": "./dist/cjs/index.js"
15
15
  }
16
16
  },
17
17
  "files": [
@@ -25,15 +25,16 @@
25
25
  "@babel/preset-env": "^7.20.2",
26
26
  "@babel/preset-react": "^7.18.6",
27
27
  "@size-limit/preset-small-lib": "^8.2.4",
28
- "@testing-library/react": "^14.0.0",
28
+ "@testing-library/dom": "^10.4.0",
29
+ "@testing-library/react": "^16.2.0",
29
30
  "@types/jest": "^29.4.0",
30
- "@types/node": "^18.13.0",
31
+ "@types/node": "^20.0.0",
31
32
  "@types/probe-image-size": "^7.2.0",
32
33
  "@types/react": "^18.0.28",
33
34
  "@typescript-eslint/eslint-plugin": "^5.54.1",
34
35
  "@typescript-eslint/parser": "^5.54.1",
35
36
  "babel-jest": "^29.5.0",
36
- "esbuild": "^0.17.8",
37
+ "esbuild": "^0.24.2",
37
38
  "eslint": "^8.34.0",
38
39
  "eslint-config-prettier": "^8.6.0",
39
40
  "eslint-config-standard": "^17.0.0",
@@ -48,21 +49,14 @@
48
49
  "jest-environment-jsdom": "^29.5.0",
49
50
  "prettier": "^2.8.4",
50
51
  "probe-image-size": "^7.2.3",
51
- "react": "^18.2.0",
52
- "react-dom": "^18.2.0",
53
- "react-test-renderer": "^18.2.0",
52
+ "react": "^18.3.0",
53
+ "react-dom": "^18.3.0",
54
+ "react-test-renderer": "^18.3.0",
54
55
  "rimraf": "^4.1.2",
55
56
  "size-limit": "^8.2.4",
56
57
  "ts-jest": "^29.1.0",
57
58
  "ts-node": "^10.9.1",
58
- "typescript": "^5.0.3"
59
- },
60
- "scripts": {
61
- "build": "rimraf dist && node ./scripts/build.js && tsc -p tsconfig.build.json",
62
- "postbuild": "./scripts/postbuild.sh",
63
- "size": "size-limit",
64
- "test": "jest",
65
- "test:generate-measurements": "yarn build && yarn node tests/buildTestCases.js"
59
+ "typescript": "^5.6.0"
66
60
  },
67
61
  "size-limit": [
68
62
  {
@@ -74,5 +68,12 @@
74
68
  "limit": "3 kB"
75
69
  }
76
70
  ],
77
- "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
78
- }
71
+ "scripts": {
72
+ "build": "rimraf dist && node ./scripts/build.js && tsc -p tsconfig.build.json",
73
+ "postbuild": "./scripts/postbuild.sh",
74
+ "size": "size-limit",
75
+ "test": "jest",
76
+ "test:generate-measurements": "yarn build && yarn node tests/buildTestCases.js",
77
+ "lint": "eslint src --ext .ts,.tsx"
78
+ }
79
+ }
@@ -1,25 +0,0 @@
1
- export type PropsOf<C extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>> = JSX.LibraryManagedAttributes<C, React.ComponentPropsWithoutRef<C>>;
2
- export type AsProp<C extends React.ElementType> = {
3
- /**
4
- * By default, the component will render an `<img>` tag. You can override this
5
- * by passing a different component or HTML tag name.
6
- */
7
- as?: C;
8
- };
9
- /**
10
- * Allows for extending a set of props (`ExtendedProps`) by an overriding set of props
11
- * (`OverrideProps`), ensuring that any duplicates are overridden by the overriding
12
- * set of props.
13
- */
14
- export type ExtendableProps<ExtendedProps = object, OverrideProps = object> = OverrideProps & Omit<ExtendedProps, keyof OverrideProps>;
15
- /**
16
- * Allows for inheriting the props from the specified element type so that
17
- * props like children, className & style work, as well as element-specific
18
- * attributes like aria roles. The component (`C`) must be passed in.
19
- */
20
- export type InheritableElementProps<C extends React.ElementType, Props = object> = ExtendableProps<PropsOf<C>, Props>;
21
- /**
22
- * A more sophisticated version of `InheritableElementProps` where
23
- * the passed in `as` prop will determine which props can be included
24
- */
25
- export type PolymorphicComponentProps<C extends React.ElementType, Props = object> = InheritableElementProps<C, Props & AsProp<C>>;