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 +38 -10
- package/dist/ImageWithPreview.d.ts +1 -1
- package/dist/SanityImage.d.ts +3 -3
- package/dist/assetId.d.ts +20 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/mjs/index.js +1 -1
- package/dist/mjs/index.js.map +4 -4
- package/dist/types.d.ts +62 -19
- package/dist/urlBuilder.d.ts +3 -18
- package/package.json +21 -20
- package/dist/polymorphic-voodoo.d.ts +0 -25
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.
|
|
229
|
-
|
|
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
|
-
|
|
232
|
-
import
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
8
|
+
export declare const ImageWithPreview: <T extends React.ElementType = "img">({ as, preview, style, alt, ...props }: ImageWithPreviewProps<T>) => React.JSX.Element;
|
package/dist/SanityImage.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import React, { type
|
|
2
|
-
import type {
|
|
3
|
-
export declare const SanityImage: <
|
|
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
|
|
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
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -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": "0jBAAA,IAAAA,
|
|
6
|
-
"names": ["
|
|
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
|
|
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
|
package/dist/mjs/index.js.map
CHANGED
|
@@ -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,
|
|
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", "
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
7
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
31
|
-
*
|
|
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
|
|
36
|
-
*
|
|
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;
|
package/dist/urlBuilder.d.ts
CHANGED
|
@@ -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
|
|
19
|
-
}
|
|
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.
|
|
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/
|
|
28
|
+
"@testing-library/dom": "^10.4.0",
|
|
29
|
+
"@testing-library/react": "^16.2.0",
|
|
29
30
|
"@types/jest": "^29.4.0",
|
|
30
|
-
"@types/node": "^
|
|
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.
|
|
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.
|
|
52
|
-
"react-dom": "^18.
|
|
53
|
-
"react-test-renderer": "^18.
|
|
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
|
|
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
|
-
"
|
|
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>>;
|