sanity-image 0.1.0 → 0.1.2
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 +384 -0
- package/dist/ImageWithPreview.d.ts +1 -1
- package/dist/SanityImage.d.ts +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +4 -4
- package/dist/index.d.ts +2 -3
- package/dist/mjs/index.js +1 -1
- package/dist/mjs/index.js.map +4 -4
- package/dist/parseImageId.d.ts +18 -0
- package/dist/polymorphic-voodoo.d.ts +25 -0
- package/dist/types.d.ts +172 -100
- package/dist/urlBuilder.d.ts +32 -0
- package/package.json +34 -7
- package/dist/.DS_Store +0 -0
- package/dist/builder.d.ts +0 -6
- package/dist/imageUrls.d.ts +0 -19
- package/dist/parseImageRef.d.ts +0 -9
package/README.md
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# sanity-image
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/sanity-image)
|
|
4
|
+

|
|
5
|
+
[](https://github.com/coreyward/sanity-image/issues)
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
A well-considered React component for displaying images from Sanity. At a
|
|
9
|
+
glance:
|
|
10
|
+
|
|
11
|
+
- Outputs a single `<img>` tag, no nested DOM structure to mess with
|
|
12
|
+
- Zero styling included so you can style it however you want…it's just an `img`
|
|
13
|
+
tag!
|
|
14
|
+
- Supports low-quality image previews out of the box, without build-time
|
|
15
|
+
penalties (native lazy loading)
|
|
16
|
+
- Generates a `srcSet` automatically based on the `width` you specify
|
|
17
|
+
- Dynamic `srcSet` factor based on image output width
|
|
18
|
+
- Knows _exactly_ what size the image will be and sets `width` and `height`
|
|
19
|
+
attributes accordingly
|
|
20
|
+
- Supports `crop` and `hotspot` values from the Sanity Studio
|
|
21
|
+
- Automatically crops to the most “interesting” part of the image if the aspect
|
|
22
|
+
ratio changes and no `hotspot` is provided
|
|
23
|
+
- Images are _never_ scaled up
|
|
24
|
+
- Tiny 4kb bundle size (2kb gzipped)
|
|
25
|
+
- No dependencies
|
|
26
|
+
- TypeScript support
|
|
27
|
+
- Works with Gatsby, Next.js, and any other React-based framework
|
|
28
|
+
- Polymorphic component (supports `as` prop to render as a custom component)
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Install it:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
yarn add sanity-image
|
|
36
|
+
# or
|
|
37
|
+
npm install sanity-image
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Use it:
|
|
41
|
+
|
|
42
|
+
You can find the full writeup on getting going below, but in the interest of
|
|
43
|
+
making it easy to see if this is the thing you are looking for, here’s a quick
|
|
44
|
+
example of most of what you’ll need to know:
|
|
45
|
+
|
|
46
|
+
**Simplest Case**:
|
|
47
|
+
|
|
48
|
+
This will render the image out assuming it will be displayed at half its
|
|
49
|
+
original width with a srcSet included (multiplies vary based on original image
|
|
50
|
+
size):
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { SanityImage } from "sanity-image"
|
|
54
|
+
|
|
55
|
+
const YourSweetComponent = ({ image }: ComponentProps) => (
|
|
56
|
+
<Image
|
|
57
|
+
// Pass the Sanity Image ID (`_id`) (e.g., `image-abcde12345-1200x800-jpg`)
|
|
58
|
+
id={image._id}
|
|
59
|
+
baseUrl="https://cdn.sanity.io/images/abcd1234/production"
|
|
60
|
+
alt="Demo image"
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**More full-featured example**:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { SanityImage } from "sanity-image"
|
|
69
|
+
|
|
70
|
+
const YourSweetComponent = ({ image }: ComponentProps) => (
|
|
71
|
+
<Image
|
|
72
|
+
// Pass the Sanity Image ID (`_id`) (e.g., `image-abcde12345-1200x800-jpg`)
|
|
73
|
+
id={image._id}
|
|
74
|
+
//
|
|
75
|
+
// You can set the base URL manually, or let it be constructed by passing
|
|
76
|
+
// `projectId` and `dataset` props.
|
|
77
|
+
baseUrl="https://cdn.sanity.io/images/abcd1234/production"
|
|
78
|
+
//
|
|
79
|
+
// Specify how big it is expected to render so a reasonable srcSet can be
|
|
80
|
+
// generated using `width`, `height`, or both
|
|
81
|
+
width={500}
|
|
82
|
+
height={250}
|
|
83
|
+
//
|
|
84
|
+
// Choose whether you want it to act like `object-fit: cover` or
|
|
85
|
+
// `object-fit: contain`, or leave it out to use the default (contain)
|
|
86
|
+
mode="cover"
|
|
87
|
+
//
|
|
88
|
+
// Have hotspot or crop data from Sanity? Pass it in!
|
|
89
|
+
hotspot={image.hotspot}
|
|
90
|
+
crop={image.crop}
|
|
91
|
+
//
|
|
92
|
+
// Want low-quality image previews? Fetch them from Sanity and pass them in too.
|
|
93
|
+
preview={image.asset.metadata.lqip}
|
|
94
|
+
//
|
|
95
|
+
// Have a burning desire to have Sanity change the format or something?
|
|
96
|
+
// Most of the visual effects from the Sanity Image API are available:
|
|
97
|
+
queryParams={{ sharpen: 30, q: 80 }}
|
|
98
|
+
//
|
|
99
|
+
// Anything else you want to pass through to the img tag? Go for it!
|
|
100
|
+
alt="Sweet Christmas!"
|
|
101
|
+
className="big-ol-image"
|
|
102
|
+
sizes="(min-width: 500px) 500px, 100vw"
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
export default YourSweetComponent
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
That’s the gist. Read on for more. 👇
|
|
110
|
+
|
|
111
|
+
## Details
|
|
112
|
+
|
|
113
|
+
How it works at a glance:
|
|
114
|
+
|
|
115
|
+
- The image ID is parsed to determine the source image dimensions and format
|
|
116
|
+
- SVG images get special treatment from the Sanity Image API (they don't support
|
|
117
|
+
params), so they're handled a bit differently (check `SanityImage.ts` for
|
|
118
|
+
details)
|
|
119
|
+
- All other images have `src` and `srcSet` props generated based on the `width`
|
|
120
|
+
and `height` props you pass in (or the image dimensions if you don't pass in a
|
|
121
|
+
width or height)
|
|
122
|
+
- The `srcSet` widths depend on the size of the output image and the original
|
|
123
|
+
image; there's some logic to avoid wasteful tiny images or giant jumps in size
|
|
124
|
+
between large entries (see `dynamicMultipliers` in `urlBuilder.ts`)
|
|
125
|
+
- Values in the `srcSet` are never duplicated and never upscale the image
|
|
126
|
+
- Since we can compute the output dimensions of the image in all cases, the
|
|
127
|
+
`width` and `height` attributes are set automatically to avoid layout shifts
|
|
128
|
+
- A few image params are applied by default:
|
|
129
|
+
- `auto=format` - Sanity will use WebP images if they're supported by the
|
|
130
|
+
browser (note: if you specify `fm` manually, this won't be set)
|
|
131
|
+
- `fit` - if the image aspect ratio isn't changed, this will be set to `max`;
|
|
132
|
+
if the aspect ratio will change it's set to `crop`; you don't really need to
|
|
133
|
+
worry about this though
|
|
134
|
+
- `q` - the quality is set to 75 by default, but you can override it with the
|
|
135
|
+
`queryParams` prop
|
|
136
|
+
- The `loading` attribute will be set to `lazy` if it isn't supplied; use
|
|
137
|
+
`loading="eager"` for images above the fold
|
|
138
|
+
- The `alt` attribute will be set to an empty string if it isn't supplied; set
|
|
139
|
+
it if it isn't a decorative image!
|
|
140
|
+
- By default it renders an `img` tag (two if you pass in a `preview`), but you
|
|
141
|
+
can pass in a custom component to render as using the `as` prop (see the
|
|
142
|
+
`SanityImage.test.tsx` file for an example of this)
|
|
143
|
+
- If you wanna get weird you can also import the `buildSrc` and `buildSrcSet`
|
|
144
|
+
exports to do your own thing with. You get a lot of the magic still this way
|
|
145
|
+
with a skosh more control.
|
|
146
|
+
- Similarly, the `parseImageId` function is available as a named export; it
|
|
147
|
+
takes an image ID and returns an object with the image id, dimensions, and
|
|
148
|
+
format.
|
|
149
|
+
- Query params passed to Sanity are all sorted and minimized like heck for
|
|
150
|
+
improved caching and smaller URLs. Pass in a `height` only? Don't be alarmed,
|
|
151
|
+
but it'll be converted to a `w` param without altering what you're asking
|
|
152
|
+
Sanity for. Ask for `mode="cover"` but the aspect ratio matches the source?
|
|
153
|
+
It'll be ignored and fall back to `fit=max` with just a `w` param. You get the
|
|
154
|
+
idea (I hope, or at least, I'm pretending, but no judgement if you don't, it's
|
|
155
|
+
definitely 11:09pm and I'm on fumes)
|
|
156
|
+
|
|
157
|
+
## Props
|
|
158
|
+
|
|
159
|
+
This is mostly copied and reformatted from the `types.ts` file; if you're
|
|
160
|
+
comfortable with TypeScript, that might give you more detail.
|
|
161
|
+
|
|
162
|
+
- `id` (string) — Required - The Sanity Image ID (`_id` or `_ref` field value)
|
|
163
|
+
- `mode` ("cover" | "contain") — Optional - Use `cover` to crop the image to
|
|
164
|
+
match the requested aspect ratio (based on `width` and `height`). Use
|
|
165
|
+
`contain` to fit the image to the boundaries provided without altering the
|
|
166
|
+
aspect ratio. Defaults to `"contain"`.
|
|
167
|
+
- `width` (number) — Optional - The target width of the image in pixels. Only
|
|
168
|
+
used for determining the dimensions of the generated assets, not for layout.
|
|
169
|
+
Use CSS to specify how the browser should render the image instead.
|
|
170
|
+
- `height` (number) — Optional - The target height of the image in pixels. Only
|
|
171
|
+
used for determining the dimensions of the generated assets, not for layout.
|
|
172
|
+
Use CSS to specify how the browser should render the image instead.
|
|
173
|
+
- `hotspot` (`{ x: number, y: number }`) — Optional - The hotspot coordinates to
|
|
174
|
+
use for the image. Note: hotspot `width` and `height` are not used.
|
|
175
|
+
- `crop` (`{ top: number, bottom: number, left: number, right: number }`) —
|
|
176
|
+
Optional - The crop coordinates to use for the image.
|
|
177
|
+
- `preview` (string) — Optional - A low-quality image preview to use while the
|
|
178
|
+
full-size image is loading. This should be a base64-encoded image string.
|
|
179
|
+
- `as` (React.ElementType) — Optional - The component to render as. Defaults to
|
|
180
|
+
`"img"`.
|
|
181
|
+
- `baseUrl` (string) — Optional - The base URL to use for the image. If not
|
|
182
|
+
specified, the `projectId` and `dataset` props will be used to construct the
|
|
183
|
+
URL.
|
|
184
|
+
- `projectId` (string) — Optional - The Sanity project ID to use for the image.
|
|
185
|
+
Only used if `baseUrl` is not specified.
|
|
186
|
+
- `dataset` (string) — Optional - The Sanity dataset to use for the image. Only
|
|
187
|
+
used if `baseUrl` is not specified.
|
|
188
|
+
- `queryParams` (object) — Optional - An object of query parameters to pass to
|
|
189
|
+
the Sanity Image API. See the
|
|
190
|
+
[Sanity Image API documentation](https://www.sanity.io/docs/image-urls) for a
|
|
191
|
+
list of available options.
|
|
192
|
+
|
|
193
|
+
That's the gist. There's a ton more in the inline comments and types and such,
|
|
194
|
+
and I'll add more details as I think of them. Feel free to open an issue or
|
|
195
|
+
start a discussion if you have questions or suggestions, or find me on the
|
|
196
|
+
Sanity Slack!
|
|
197
|
+
|
|
198
|
+
<details>
|
|
199
|
+
<summary><strong>⚠️ Minor gotchas with deferred loading</strong></summary>
|
|
200
|
+
|
|
201
|
+
`SanityImage` is relying on browser-native deferred image loading. This
|
|
202
|
+
generally works fine in browsers that support it, but there are situations where
|
|
203
|
+
the unloaded image is hidden or covered, resulting in the full image never
|
|
204
|
+
loading.
|
|
205
|
+
|
|
206
|
+
If this happens, you can override the styles set on the full-size image using
|
|
207
|
+
the `img[data-loading]` selector. This image sits immediately adjacent to the
|
|
208
|
+
spaceball image and has the following default styles _while loading_:
|
|
209
|
+
|
|
210
|
+
```css
|
|
211
|
+
position: absolute;
|
|
212
|
+
width: 10px !important; /* must be > 4px to be lazy loaded */
|
|
213
|
+
height: 10px !important; /* must be > 4px to be lazy loaded */
|
|
214
|
+
opacity: 0;
|
|
215
|
+
zindex: -10;
|
|
216
|
+
pointerevents: none;
|
|
217
|
+
userselect: none;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
</details>
|
|
221
|
+
|
|
222
|
+
## Tips
|
|
223
|
+
|
|
224
|
+
### Wrap it internally
|
|
225
|
+
|
|
226
|
+
I recommend creating a wrapper component internally to pass your `baseUrl` prop
|
|
227
|
+
and pass through any props. This keeps the configuration in one place and gives
|
|
228
|
+
you an entry point to add any other logic you might need. Here's a TypeScript
|
|
229
|
+
example (for JavaScript, just remove the type annotation after `props`):
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
import { SanityImage } from "sanity-image"
|
|
233
|
+
|
|
234
|
+
const projectId = process.env.SANITY_PROJECT_ID
|
|
235
|
+
const dataset = process.env.SANITY_DATASET
|
|
236
|
+
const baseUrl = `https://cdn.sanity.io/images/${projectId}/${dataset}`
|
|
237
|
+
|
|
238
|
+
export const Image = (
|
|
239
|
+
props: Omit<
|
|
240
|
+
React.ComponentProps<typeof SanityImage>,
|
|
241
|
+
"baseUrl" | "dataset" | "projectId"
|
|
242
|
+
>
|
|
243
|
+
) => <SanityImage baseUrl={baseUrl} {...props} />
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Styling your images
|
|
247
|
+
|
|
248
|
+
I recommend setting something like the following CSS for images in your project,
|
|
249
|
+
then overriding styles as needed. This will ensure images act like block-level
|
|
250
|
+
elements with infinitely scalable contents even with the `width` and `height`
|
|
251
|
+
attributes set. It also makes it easier to handle responsiveness—if your
|
|
252
|
+
container gets smaller, the image gets smaller.
|
|
253
|
+
|
|
254
|
+
```css
|
|
255
|
+
img {
|
|
256
|
+
display: block;
|
|
257
|
+
max-width: 100%;
|
|
258
|
+
width: 100%;
|
|
259
|
+
height: auto;
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Here's an example of how that works when using, for example, a 3-column grid
|
|
264
|
+
that fills the viewport until it is a maximum of 1,200px wide (plus padding).
|
|
265
|
+
This produces columns that are 390px at most on desktop:
|
|
266
|
+
|
|
267
|
+
```jsx
|
|
268
|
+
<div
|
|
269
|
+
css={{
|
|
270
|
+
display: "grid",
|
|
271
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
272
|
+
gap: 15,
|
|
273
|
+
maxWidth: 1240,
|
|
274
|
+
paddingInline: 20,
|
|
275
|
+
marginInline: "auto",
|
|
276
|
+
}}
|
|
277
|
+
>
|
|
278
|
+
{["image-a", "image-b", "image-c"].map((imageId) => (
|
|
279
|
+
<div key={imageId}>
|
|
280
|
+
<SanityImage
|
|
281
|
+
id={imageId}
|
|
282
|
+
baseUrl="..."
|
|
283
|
+
width={390}
|
|
284
|
+
sizes="(min-width: 1240px) 390px, calc((100vw - 40px - 30px) / 3)"
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
))}
|
|
288
|
+
</div>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
If you need these images to all match in height, it's a good idea to switch to
|
|
292
|
+
`cover` mode. With the height set to 260px and `mode="cover"`, this will produce
|
|
293
|
+
images with a 3:2 aspect ratio that fill the column width even if the source
|
|
294
|
+
image is too small:
|
|
295
|
+
|
|
296
|
+
```jsx
|
|
297
|
+
<SanityImage
|
|
298
|
+
id={imageId}
|
|
299
|
+
baseUrl="..."
|
|
300
|
+
width={390}
|
|
301
|
+
height={260}
|
|
302
|
+
mode="cover"
|
|
303
|
+
sizes="(min-width: 1240px) 390px, calc((100vw - 40px - 30px) / 3)"
|
|
304
|
+
/>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
In this example we don't pass a `hotspot` value, so the image will be cropped
|
|
308
|
+
based on what Sanity thinks is the most interesting part of the image since
|
|
309
|
+
`SanityImage` automatically sets `crop=entropy` in these cases. If you want to
|
|
310
|
+
override that, you can pass a `hotspot` value.
|
|
311
|
+
|
|
312
|
+
### Background images
|
|
313
|
+
|
|
314
|
+
Using `SanityImage` for background images is easy, you just style the image to
|
|
315
|
+
match the expectations of your mockup. In most cases that means setting
|
|
316
|
+
`position: relative` on the container you want to fill, then using absolute
|
|
317
|
+
positioning for the image. Here’s an example:
|
|
318
|
+
|
|
319
|
+
```jsx
|
|
320
|
+
<section
|
|
321
|
+
css={{
|
|
322
|
+
position: "relative",
|
|
323
|
+
paddingBlock: 100,
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
<SanityImage
|
|
327
|
+
id="..."
|
|
328
|
+
baseUrl="..."
|
|
329
|
+
width={1440}
|
|
330
|
+
css={{
|
|
331
|
+
position: "absolute",
|
|
332
|
+
top: 0,
|
|
333
|
+
left: 0,
|
|
334
|
+
width: "100%",
|
|
335
|
+
height: "100%",
|
|
336
|
+
objectFit: "cover",
|
|
337
|
+
userSelect: "none",
|
|
338
|
+
zIndex: 1,
|
|
339
|
+
}}
|
|
340
|
+
alt=""
|
|
341
|
+
/>
|
|
342
|
+
|
|
343
|
+
<div css={{ position: "relative", zIndex: 2 }}>
|
|
344
|
+
<h1>Your big hero copy</h1>
|
|
345
|
+
<LinkButton to="/signup/">Get started</LinkButton>
|
|
346
|
+
</div>
|
|
347
|
+
</section>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
This will cause the `section` to be sized based on the content inside of the
|
|
351
|
+
`div`, and the image will be sized to fill the entire section. The aspect ratio
|
|
352
|
+
of the image will be maintained due to the use of `object-fit: cover`. Note that
|
|
353
|
+
we are still using `mode="contain"` for `SanityImage` here. If you have a rough
|
|
354
|
+
idea of the height your section, you can set `height` and `mode="cover"` which
|
|
355
|
+
will prevent, for example, a portrait orientation image from being retrieved and
|
|
356
|
+
cropped by the browser.
|
|
357
|
+
|
|
358
|
+
Since the z-index is set higher on the `div` containing the content, it will
|
|
359
|
+
show above the image. This example also sets `user-select: none` on the image to
|
|
360
|
+
prevent the image from being selected when the user clicks and drags on the page
|
|
361
|
+
to make it behave more like a traditional background image.
|
|
362
|
+
|
|
363
|
+
### Fetching data from Sanity via GROQ
|
|
364
|
+
|
|
365
|
+
If you're using Sanity's GROQ query language to fetch data, here is how I
|
|
366
|
+
recommend fetching the fields you need from a typical image with the hotspot,
|
|
367
|
+
crop, and low-quality image preview included:
|
|
368
|
+
|
|
369
|
+
```groq
|
|
370
|
+
"id": asset._ref,
|
|
371
|
+
"preview": asset->metadata.lqip,
|
|
372
|
+
hotspot { x, y },
|
|
373
|
+
crop {
|
|
374
|
+
bottom,
|
|
375
|
+
left,
|
|
376
|
+
right,
|
|
377
|
+
top,
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## License
|
|
382
|
+
|
|
383
|
+
Copyright ©2023 Corey Ward. Available under the
|
|
384
|
+
[MIT License](https://github.com/coreyward/sanity-image/blob/main/LICENSE).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { ImageWithPreviewProps } from "./types";
|
|
2
|
+
import type { ImageWithPreviewProps } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Renders two image tags, one with the preview image and one with the full
|
|
5
5
|
* image. When the full image is loaded, the preview image is removed, revealing
|
package/dist/SanityImage.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import React, { type ReactElement } from "react";
|
|
2
|
-
import {
|
|
3
|
-
export declare const SanityImage: <
|
|
2
|
+
import type { PolymorphicComponentProps, SanityImageProps } from "./types";
|
|
3
|
+
export declare const SanityImage: <C extends React.ElementType<any> = "img">({ as: component, baseUrl, projectId, dataset, id, hotspot, crop, width, height, mode, preview, htmlWidth, htmlHeight, htmlId, queryParams, ...rest }: PolymorphicComponentProps<C, SanityImageProps>) => ReactElement;
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var _=Object.create;var x=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var k=(r,t)=>{for(var e in t)x(r,e,{get:t[e],enumerable:!0})},T=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of G(t))!Y.call(r,o)&&o!==e&&x(r,o,{get:()=>t[o],enumerable:!(n=B(t,o))||n.enumerable});return r};var Q=(r,t,e)=>(e=r!=null?_(J(r)):{},T(t||!r||!r.__esModule?x(e,"default",{value:r,enumerable:!0}):e,r)),F=r=>T(x({},"__esModule",{value:!0}),r);var V={};k(V,{ImageWithPreview:()=>S,SanityImage:()=>O,buildSrc:()=>h,buildSrcSet:()=>P,parseImageId:()=>y});module.exports=F(V);var H=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,y=r=>{let t=H.exec(r),[,e,n,o]=t!=null?t:[];if(!t||!e||!n||!o)throw new Error(`Could not parse image ID "${r}"`);let[a,m]=n.split("x").map(d=>Number.parseInt(d,10));if(Number.isNaN(a)||Number.isNaN(m)||!a||!m)throw new Error(`Invalid dimensions "${n}"`);return{assetId:e,dimensions:{height:m,width:a,aspectRatio:a/m},format:o}},w=r=>{let t=r.lastIndexOf("-");return r.slice(6,t)+"."+r.slice(t+1)};var h=({baseUrl:r,...t})=>{let{metadata:e,...n}=M({...t,options:{includeMetadata:!0}});if(!e)throw new Error("Missing image output metadata");return{src:`${`${r}${w(t.id)}`}?${L(n)}`,width:e.outputDimensions.width,height:e.outputDimensions.height}},P=({id:r,mode:t="contain",width:e,height:n,hotspot:o,crop:a,baseUrl:m})=>{let{w:d,h:u}=M({id:r,mode:t,width:e,height:n,hotspot:o,crop:a}),I=`${m}${w(r)}`,g=K(d).map(s=>{let i=Math.round(d*s),c=u&&Math.round(u*s);if(s<1&&i<50)return null;let l=M({id:r,mode:t,width:i,height:c,hotspot:o,crop:a});return`${I}?${L(l)} ${l.w}w`}).filter(Boolean);return Array.from(new Set(g))},q=({id:r,baseUrl:t})=>{let{assetId:e,dimensions:n,format:o}=y(r);return{src:`${t}${e}-${n.width}x${n.height}.${o}`,width:n.width,height:n.height}},K=r=>r<160?[.5,1,2]:r<750?[.5,1,1.5,2]:r<1400?[.25,.5,.75,1,1.5,2]:[.25,.5,.75,1,1.25,1.5,1.75,2],M=({id:r,mode:t="contain",width:e,height:n,hotspot:o,crop:a,queryParams:m,options:{includeMetadata:d=!1}={}})=>{let u=y(r).dimensions,{width:I,height:g,aspectRatio:s}=a?j(u,a):u;if(e||(n?(e=Math.round(n*s),n=void 0):e=Math.round(I/2)),t==="cover"&&(!e||!n||e/n===s)?t="contain":t==="contain"&&n&&(e=Math.min(e,Math.round(n*s)),n=void 0),e>I||n&&n>g){let c=n?e/n:s;c>=s?(e=I,n=n&&Math.round(e/c)):(n=g,e=Math.round(n*c))}let i={w:e,q:75,...m};if(i.fm||(i.auto="format"),a&&(i.rect=U(u,a)),t==="cover")if(i.fit="crop",n&&(i.h=n),o){let c=a?o.x/(1-a.left-a.right):o.x,l=a?o.y/(1-a.top-a.bottom):o.y;i["fp-x"]=W(A(c,0,1),3),i["fp-y"]=W(A(l,0,1),3)}else i.crop="entropy";else i.fit="max";if(d){let c=n||Math.round(e/s);i.metadata={sourceDimensions:u,outputDimensions:{width:e,height:c,aspectRatio:e/c}}}return i},A=(r,t,e)=>Math.max(t,Math.min(e,r)),W=(r,t)=>Math.round(r*Math.pow(10,t))/Math.pow(10,t),j=(r,t)=>{if(t.left+t.right>=1||t.top+t.bottom>=1)throw new Error(`Invalid crop: ${JSON.stringify(t)}; crop values must be less than 1`);let e=Math.round(r.width*(1-t.left-t.right)),n=Math.round(r.height*(1-t.top-t.bottom)),o=e/n;return{width:e,height:n,aspectRatio:o}},U=(r,t)=>{let{width:e,height:n}=j(r,t);return[Math.round(t.left*r.width),Math.round(t.top*r.height),e,n].join(",")},L=r=>Object.entries(r).sort(([t],[e])=>t.localeCompare(e)).map(([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`).join("&").replace(/%2C/g,",");var p=Q(require("react")),S=({as:r,preview:t,...e})=>{let[n,o]=(0,p.useState)(!1),a=(0,p.useRef)(null),m=()=>{o(!0)};(0,p.useEffect)(()=>{var u;(u=a.current)!=null&&u.complete&&m()});let d=r||"img";return p.default.createElement(p.default.Fragment,null,!n&&p.default.createElement(d,{alt:e.alt,className:e.className,"data-lqip":!0,height:e.height,id:e.id,src:t,style:e.style,width:e.width}),p.default.createElement(d,{"data-loading":n?null:!0,onLoad:m,ref:a,style:n?void 0:{height:"10px !important",opacity:0,pointerEvents:"none",position:"absolute",userSelect:"none",width:"10px !important",zIndex:-10},...e}))};var $=Q(require("react"));var O=({as:r,baseUrl:t,projectId:e,dataset:n,id:o,hotspot:a,crop:m,width:d,height:u,mode:I="contain",preview:g,htmlWidth:s,htmlHeight:i,htmlId:c,queryParams:l,...b})=>{var D,N;if(!o)throw new Error("Missing required `id` prop for <SanityImage>.");if(!t&&(!e||!n))throw new Error("Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.");t=t!=null?t:`https://cdn.sanity.io/images/${e}/${n}/`;let C=o.endsWith("-svg"),E=g&&!C?S:r!=null?r:"img",f={alt:(D=b.alt)!=null?D:"",loading:(N=b.loading)!=null?N:"lazy",id:c,...b};if(C)return $.default.createElement(E,{...q({id:o,baseUrl:t}),...f});let v={baseUrl:t,id:o,crop:m,hotspot:a,width:d,height:u,mode:I,queryParams:l},{src:z,...R}=h(v);return f.srcSet=P(v).join(", "),f.src=z,f.width=s!=null?s:R.width,f.height=i!=null?i:R.height,g&&(f.as=r!=null?r:"img",f.preview=g),$.default.createElement(E,{...f})};0&&(module.exports={ImageWithPreview,SanityImage,buildSrc,buildSrcSet,parseImageId});
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/index.ts", "../../src/
|
|
4
|
-
"sourcesContent": ["export { createBuilder } from \"./builder\"\nexport { imageUrl, buildSource, buildSourceSet } from \"./imageUrls\"\nexport { ImageWithPreview } from \"./ImageWithPreview\"\nexport { parseImageRef } from \"./parseImageRef\"\nexport { SanityImage } from \"./SanityImage\"\n", "import sanityImageUrl from \"@sanity/image-url\"\n\ntype BuilderConfig = {\n dataset: string\n projectId: string\n}\n\nexport const createBuilder = ({ dataset, projectId }: BuilderConfig) =>\n sanityImageUrl({ dataset, projectId })\n", "import { ImageRefParts } from \"./types\"\n\nexport const SANITY_REF_PATTERN = /^image-([\\da-f]+)-(\\d+x\\d+)-(\\w+)$/\n\n/**\n * Parse an image reference string into its component parts.\n *\n * @param {string} id The image reference string to parse in the format `image-<hash>-<width>x<height>.<ext>`\n * @returns {ImageRefParts} An object containing the asset ID, dimensions, and format\n */\nexport const parseImageRef = (id: string): ImageRefParts => {\n const match = SANITY_REF_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 },\n format,\n }\n}\n", "import { type ImageUrlBuilder } from \"@sanity/image-url/lib/types/builder\"\nimport { parseImageRef } from \"./parseImageRef\"\nimport { Asset, ImageBuilderParameter, ImageBuilderParameters } from \"./types\"\n\nexport const DEFAULT_IMAGE_CONFIG: ImageBuilderParameters = {\n auto: \"format\",\n fit: \"max\",\n quality: 75,\n}\n\n/**\n * Returns the default `src` for the image based on the expected width (and\n * height) of the displayed image.\n */\nexport const buildSource = (\n asset: Asset,\n builder: ImageUrlBuilder,\n { width, height, ...config }: ImageBuilderParameters\n): string => {\n const { dimensions } = parseImageRef(asset._id)\n\n const origRatio = dimensions.width / dimensions.height\n width = width || dimensions.width\n height = height || Math.round(width / origRatio)\n\n return imageUrl(asset, builder, {\n ...config,\n height,\n width,\n })\n}\n\n/**\n * Returns a `srcSet` string for the image based on the expected width. The\n * generated `srcSet` will include images at 0.5x, 0.75x, 1x, 1.5x, and 2x the\n * expected width.\n *\n * This method also considers the FitMode configuration. If Sanity will not\n * scale up the image in the supplied mode, the `srcSet` will not include links\n * to images larger than the original image.\n */\nexport const buildSourceSet = (\n asset: Asset,\n builder: ImageUrlBuilder,\n config: ImageBuilderParameters\n): string => {\n const { dimensions } = parseImageRef(asset._id)\n const fitMode = config.fit ?? DEFAULT_IMAGE_CONFIG.fit\n\n // Determine dimensions and ratios for srcSet calculations\n const origRatio = dimensions.width / dimensions.height\n const width = config.width ?? dimensions.width\n const height = config.height ?? Math.round(width / origRatio)\n const targetRatio = width / height\n let cropRatio = origRatio\n let maxWidth = dimensions.width\n let maxHeight = dimensions.height\n\n // Compensate for dimensional changes if image was cropped in Sanity\n if (asset.crop && Object.values(asset.crop).some((n) => n > 0)) {\n const cropWidth =\n dimensions.width -\n asset.crop.left * dimensions.width -\n asset.crop.right * dimensions.width\n const cropHeight =\n dimensions.height -\n asset.crop.top * dimensions.height -\n asset.crop.bottom * dimensions.height\n\n cropRatio = cropWidth / cropHeight\n if (cropRatio > origRatio) {\n maxHeight = cropHeight\n } else {\n maxWidth = cropWidth\n }\n }\n\n return Object.values(\n [0.5, 0.75, 1, 1.5, 2].reduce((set: { [size: number]: string }, dpr) => {\n const url = imageUrl(asset, builder, { ...config, dpr })\n const size = Math.round(\n // For modes where Sanity will not scale up, determine\n // the anticipated final width based on the params\n fitMode && [\"fillmax\", \"max\", \"min\"].includes(fitMode)\n ? targetRatio < origRatio\n ? Math.min(\n (maxHeight / (height * dpr)) * (width * dpr),\n width * dpr\n )\n : Math.min(width * dpr, maxWidth)\n : width * dpr\n )\n\n // Avoid duplicate sizes in srcSet list\n if (!set[size]) {\n set[size] = `${url} ${size}w`\n }\n\n return set\n }, {})\n ).join(\", \")\n}\n\nexport const imageUrl = (\n asset: Asset,\n builder: ImageUrlBuilder,\n parameters: ImageBuilderParameters = {}\n): string => {\n const combinedParameters = { ...DEFAULT_IMAGE_CONFIG, ...parameters }\n let imageUrlBuilder = builder.image(asset)\n\n validateImageUrlParameters(Object.keys(combinedParameters), imageUrlBuilder)\n\n Object.entries(combinedParameters).forEach(([key, value]) => {\n imageUrlBuilder = Array.isArray(value)\n ? (imageUrlBuilder as any)[key](...value)\n : (imageUrlBuilder as any)[key](value)\n })\n\n return imageUrlBuilder.url()\n}\n\nfunction validateImageUrlParameters(\n keys: string[],\n imageUrlBuilder: ImageUrlBuilder\n): asserts keys is ImageBuilderParameter[] {\n if (!keys.every((key) => validateImageUrlParameter(key, imageUrlBuilder))) {\n throw new Error(`Invalid image URL parameter provided`)\n }\n}\n\nfunction validateImageUrlParameter(\n key: string,\n imageUrlBuilder: ImageUrlBuilder\n): key is ImageBuilderParameter {\n return key in imageUrlBuilder\n}\n", "import React, { useEffect, useRef, useState } from \"react\"\nimport { ImageWithPreviewProps } from \"./types\"\n\n/**\n * Renders two image tags, one with the preview image and one with the full\n * image. When the full image is loaded, the preview image is removed, revealing\n * the full image.\n */\nexport const ImageWithPreview = <T extends React.ElementType = \"img\">({\n as,\n preview,\n ...props\n}: ImageWithPreviewProps<T>) => {\n const [loaded, setLoaded] = useState(false)\n const ref = useRef<HTMLImageElement>(null)\n\n const onLoad = () => {\n setLoaded(true)\n }\n\n useEffect(() => {\n if (ref.current?.complete) {\n onLoad()\n }\n })\n\n const Img = as || \"img\"\n\n return (\n <>\n {!loaded && (\n <Img\n alt={props.alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={props.style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? undefined\n : {\n // must be > 4px to be lazy loaded\n height: \"10px !important\",\n\n // must be > 4px to be lazy loaded\n opacity: 0,\n\n pointerEvents: \"none\",\n // Cannot use negative x or y values, visibility: hidden, or display: none\n // to hide or the image might not get loaded.\n position: \"absolute\",\n userSelect: \"none\",\n width: \"10px !important\",\n zIndex: -10,\n }\n }\n {...props}\n />\n </>\n )\n}\n", "import React, {\n type ReactElement,\n type ElementType,\n type ComponentPropsWithoutRef,\n} from \"react\"\nimport { Asset, PolymorphicComponentProp, SanityImageProps } from \"./types\"\nimport { parseImageRef } from \"./parseImageRef\"\nimport { buildSource, buildSourceSet, imageUrl } from \"./imageUrls\"\nimport { ImageWithPreview } from \"./ImageWithPreview\"\n\nexport const SanityImage = <T extends ElementType = \"img\">({\n as: component,\n builder,\n\n asset,\n hotspot,\n crop,\n\n width,\n height,\n htmlWidth,\n htmlHeight,\n\n options = {},\n config = {},\n\n // Swallowing these params for convenience\n _type,\n _key,\n\n alt = \"\",\n ...rest\n}: PolymorphicComponentProp<T, SanityImageProps>): ReactElement => {\n const ImageComponent = component ?? \"img\"\n const preview = asset.metadata?.preview ?? asset.metadata?.lqip\n\n const props: typeof rest & {\n alt: string\n width?: number\n height?: number\n } = {\n ...rest,\n alt: alt || \"\",\n }\n\n // Rebuild `asset` with only the properties needed for the image\n const image: Asset = {\n _id: \"_id\" in asset ? asset._id : asset._ref,\n crop,\n hotspot,\n }\n\n // Short circuit for SVG images\n if (parseImageRef(image._id).format === \"svg\") {\n return <ImageComponent src={imageUrl(image, builder)} {...props} />\n }\n\n // Create default src and build srcSet\n const source = buildSource(image, builder, { ...config, height, width })\n const sourceSet = buildSourceSet(image, builder, { ...config, height, width })\n\n if (options.aspectRatio) {\n // If enabled, this will estimate the final aspect ratio based on\n // the dimensions of the original image and the crop parameter,\n // then use this aspect ratio to apply `width` and `height` attrs\n // to both the preview and final images.\n //\n // Note: No attempts are made to compensate for the `fit` mode or\n // image params that transform the final output dimensions in this\n // early proof-of-concept version.\n const { dimensions } = parseImageRef(image._id)\n\n // Short circuit if both width and height are set. This will result\n // in the final aspect ratio matching the aspect ration of the\n // provided width and height props, ignoring the image dimensions.\n //\n // This relies on a bug in the @sanity/image-url library that\n // results in images being cropped with fit modes where they\n // should not be.\n if (width && height) {\n props.width = width\n props.height = height\n } else {\n // If `crop` isn't set, use fallback values.\n crop = crop ?? { bottom: 0, left: 0, right: 0, top: 0 }\n\n const croppedWidth = dimensions.width * (1 - crop.left - crop.right)\n const croppedHeight = dimensions.height * (1 - crop.top - crop.bottom)\n const ratio = croppedWidth / croppedHeight\n\n props.width = width ?? dimensions.width\n props.height = Math.round(props.width / ratio)\n }\n }\n\n if (htmlWidth) props.width = htmlWidth\n if (htmlHeight) props.height = htmlHeight\n\n const Image = preview ? ImageWithPreview : ImageComponent\n\n const componentProps: ComponentPropsWithoutRef<typeof Image> = {\n ...props,\n loading: \"lazy\",\n src: source,\n srcSet: sourceSet,\n style: {\n ...props.style,\n ...(hotspot && {\n objectPosition: [hotspot.x, hotspot.y]\n .map((value) => (value * 100).toFixed(2) + \"%\")\n .join(\" \"),\n }),\n },\n }\n\n return preview ? (\n <ImageWithPreview\n {...componentProps}\n as={ImageComponent}\n preview={preview}\n />\n ) : (\n <ImageComponent {...componentProps} />\n )\n}\n"],
|
|
5
|
-
"mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,EAAA,gBAAAC,EAAA,
|
|
6
|
-
"names": ["src_exports", "__export", "ImageWithPreview", "SanityImage", "
|
|
3
|
+
"sources": ["../../src/index.ts", "../../src/parseImageId.ts", "../../src/urlBuilder.ts", "../../src/ImageWithPreview.tsx", "../../src/SanityImage.tsx"],
|
|
4
|
+
"sourcesContent": ["export { buildSrc, buildSrcSet } from \"./urlBuilder\"\nexport { ImageWithPreview } from \"./ImageWithPreview\"\nexport { parseImageId } from \"./parseImageId\"\nexport { SanityImage } from \"./SanityImage\"\n", "import type { ImageIdParts } from \"./types\"\n\nexport const SANITY_IMAGE_ID_PATTERN = /^image-([\\da-f]+)-(\\d+x\\d+)-(\\w+)$/\n\n/**\n * Parse an image id string into its component parts.\n *\n * @param {string} id The image id string to parse in the format `image-<hash>-<width>x<height>.<ext>`\n * @returns {ImageIdParts} An object containing the asset ID, dimensions, and format\n */\nexport const parseImageId = (id: string): ImageIdParts => {\n const match = SANITY_IMAGE_ID_PATTERN.exec(id)\n const [, assetId, dimensions, format] = match ?? []\n\n if (!match || !assetId || !dimensions || !format) {\n throw new Error(`Could not parse image ID \"${id}\"`)\n }\n\n const [width, height] = dimensions\n .split(\"x\")\n .map((value: string): number => Number.parseInt(value, 10))\n\n if (Number.isNaN(width) || Number.isNaN(height) || !width || !height) {\n throw new Error(`Invalid dimensions \"${dimensions}\"`)\n }\n\n return {\n assetId,\n dimensions: { height, width, aspectRatio: width / height },\n format,\n }\n}\n\n/**\n * Convert an image id to a URL path segment for the Sanity Image API. Input is\n * not validated.\n *\n * @example\n * imageIdToUrlPath(\"image-<hash>-<width>x<height>-<ext>\")\n * // => \"<hash>-<width>x<height>.<ext>\"\n */\nexport const imageIdToUrlPath = (id: string): string => {\n // This can be implemented with `parseImageId` but it's more computationally expensive\n // than this more naive implementation.\n\n const formatSeparatorIndex = id.lastIndexOf(\"-\")\n\n return (\n id.slice(6, formatSeparatorIndex) + \".\" + id.slice(formatSeparatorIndex + 1)\n )\n}\n", "import { imageIdToUrlPath, parseImageId } from \"./parseImageId\"\nimport type {\n ComputedImageData,\n CropData,\n ImageIdParts,\n ImageQueryInputs,\n ImageQueryParams,\n ImageSrcInputs,\n} from \"types\"\n\n/**\n * Convert ImageSrcInputs into a full image URL and computed output dimensions.\n */\nexport const buildSrc = ({\n baseUrl,\n ...inputParams\n}: ImageSrcInputs): ComputedImageData => {\n const { metadata, ...queryParams } = buildQueryParams({\n ...inputParams,\n options: { includeMetadata: true },\n })\n\n // Narrowing for TS\n if (!metadata) {\n throw new Error(\"Missing image output metadata\")\n }\n\n const imageUrl = `${baseUrl}${imageIdToUrlPath(inputParams.id)}`\n\n return {\n src: `${imageUrl}?${buildQueryString(queryParams)}`,\n width: metadata.outputDimensions.width,\n height: metadata.outputDimensions.height,\n }\n}\n\nexport const buildSrcSet = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n baseUrl,\n}: ImageSrcInputs) => {\n // Determine base computed width\n const { w, h } = buildQueryParams({ id, mode, width, height, hotspot, crop })\n\n // URL of the image without any query parameters\n const imageUrl = `${baseUrl}${imageIdToUrlPath(id)}`\n\n // Build srcset\n const srcSetEntries = dynamicMultipliers(w)\n .map((multiple) => {\n const computedWidth = Math.round(w * multiple)\n const computedHeight = h && Math.round(h * multiple)\n\n // Ignore tiny entries; the extra data in the HTML is almost never worth it\n if (multiple < 1 && computedWidth < 50) return null\n\n const params: Omit<ImageQueryParams, \"metadata\"> = buildQueryParams({\n id,\n mode,\n width: computedWidth,\n height: computedHeight,\n hotspot,\n crop,\n })\n\n return `${imageUrl}?${buildQueryString(params)} ${params.w}w`\n })\n .filter(Boolean)\n\n return Array.from(new Set(srcSetEntries))\n}\n\nexport const buildSvgAttributes = ({ id, baseUrl }: ImageSrcInputs) => {\n const { assetId, dimensions, format } = parseImageId(id)\n\n return {\n src: `${baseUrl}${assetId}-${dimensions.width}x${dimensions.height}.${format}`,\n width: dimensions.width,\n height: dimensions.height,\n }\n}\n\nconst dynamicMultipliers = (width: number) => {\n // For really small images, use larger steps\n if (width < 160) {\n return [0.5, 1, 2]\n }\n\n // For typical width images, use standard steps\n if (width < 750) {\n return [0.5, 1, 1.5, 2]\n }\n\n // For larger images, include 0.25x and 0.75x steps\n if (width < 1400) {\n return [0.25, 0.5, 0.75, 1, 1.5, 2]\n }\n\n // For really large images, use a wider range of steps at the low end, and smaller steps at the high end\n return [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]\n}\n\n/**\n * Constructs a query parameters object for the Sanity image URL based on the inputs provided.\n */\nexport const buildQueryParams = ({\n id,\n mode = \"contain\",\n width,\n height,\n hotspot,\n crop,\n queryParams,\n options: { includeMetadata = false } = {},\n}: ImageQueryInputs & {\n options?: {\n /** Include data about the image in the response */\n includeMetadata?: boolean\n }\n}): ImageQueryParams => {\n const sourceDimensions = parseImageId(id).dimensions\n\n // If crop is provided, compute post-crop dimensions\n const {\n width: maxWidth,\n height: maxHeight,\n aspectRatio: sourceAspectRatio,\n } = crop ? croppedImageSize(sourceDimensions, crop) : sourceDimensions\n\n // Determine width if not provided\n if (!width) {\n if (height) {\n // Compute width based on height and default ratio\n width = Math.round(height * sourceAspectRatio)\n\n // Discard `height` since we have to be in `contain` mode and we've converted it into `width`\n height = undefined\n } else {\n // Use 1/2 of the max image width by default to allow for 2x scale-up\n width = Math.round(maxWidth / 2)\n }\n }\n\n // Override `cover` mode if both width and height haven't been provided, or if\n // the requested aspect ratio matches the source aspect ratio. In these cases\n // the result will be the same as `contain` mode anyways, and `contain` mode\n // is simpler and saves a few bytes in the URL.\n if (\n mode === \"cover\" &&\n (!width || !height || width / height === sourceAspectRatio)\n ) {\n mode = \"contain\"\n } else if (mode === \"contain\" && height) {\n // Similarly, if `contain` mode is used and a height is provided, we can\n // convert it into a width by adjusting the width such that the\n // aspect-ratio\u2013constrained result will respect the height provided.\n width = Math.min(width, Math.round(height * sourceAspectRatio))\n height = undefined\n }\n\n // Clamp min and max dimensions while preserving requested aspect ratio\n if (width > maxWidth || (height && height > maxHeight)) {\n const requestedAspectRatio = height ? width / height : sourceAspectRatio\n\n if (requestedAspectRatio >= sourceAspectRatio) {\n // Clamp width\n width = maxWidth\n height = height && Math.round(width / requestedAspectRatio)\n } else {\n // Clamp height\n height = maxHeight\n width = Math.round(height * requestedAspectRatio)\n }\n }\n\n // Note: when converting params to a query string initially, we need to\n // use an object or map instead of URLSearchParams, since the latter will\n // allow multiple params with the same name, which is not supported by the\n // Sanity Image API.\n const params: Partial<ImageQueryParams> = {\n w: width,\n q: 75,\n ...queryParams,\n }\n\n // If an explicit format has not been requested, use auto format\n if (!params.fm) params.auto = \"format\"\n\n if (crop) {\n // Convert crop to rect param)\n params.rect = buildRect(sourceDimensions, crop)\n }\n\n if (mode === \"cover\") {\n params.fit = \"crop\"\n\n if (height) {\n params.h = height\n }\n\n if (hotspot) {\n // Hotspot is relative to post-`rect` dimensions; if `crop` is present,\n // the hotspot inputs need to be adjusted accordingly\n const x = crop ? hotspot.x / (1 - crop.left - crop.right) : hotspot.x\n const y = crop ? hotspot.y / (1 - crop.top - crop.bottom) : hotspot.y\n\n params[\"fp-x\"] = roundWithPrecision(clamp(x, 0, 1), 3)\n params[\"fp-y\"] = roundWithPrecision(clamp(y, 0, 1), 3)\n } else {\n // If no hotspot is provided, use Sanity\u2019s `entropy` crop mode\n params.crop = \"entropy\"\n }\n } else {\n params.fit = \"max\"\n }\n\n if (includeMetadata) {\n // Height will be set if the aspect ratio varies from `sourceAspectRatio`\n const outputHeight = height || Math.round(width / sourceAspectRatio)\n\n params.metadata = {\n sourceDimensions,\n outputDimensions: {\n width,\n height: outputHeight,\n aspectRatio: width / outputHeight,\n },\n }\n }\n\n return <ImageQueryParams>params\n}\n\nconst clamp = (value: number, min: number, max: number) =>\n Math.max(min, Math.min(max, value))\nconst roundWithPrecision = (value: number, precision: number) =>\n Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision)\n\nexport const croppedImageSize = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n): ImageIdParts[\"dimensions\"] => {\n if (crop.left + crop.right >= 1 || crop.top + crop.bottom >= 1) {\n throw new Error(\n `Invalid crop: ${JSON.stringify(crop)}; crop values must be less than 1`\n )\n }\n\n const width = Math.round(dimensions.width * (1 - crop.left - crop.right))\n const height = Math.round(dimensions.height * (1 - crop.top - crop.bottom))\n const aspectRatio = width / height\n\n return { width, height, aspectRatio }\n}\n\n/**\n * Build a `rect` value to crop the image.\n */\nexport const buildRect = (\n /** Source/original image dimensions */\n dimensions: { width: number; height: number },\n crop: CropData\n) => {\n const { width, height } = croppedImageSize(dimensions, crop)\n\n return [\n Math.round(crop.left * dimensions.width),\n Math.round(crop.top * dimensions.height),\n width,\n height,\n ].join(\",\")\n}\n\nexport const buildQueryString = (params: Record<string, string | number>) =>\n Object.entries(params)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(value)}`\n )\n .join(\"&\")\n .replace(/%2C/g, \",\") // don't urlencode commas\n", "import React, { useEffect, useRef, useState } from \"react\"\nimport type { ImageWithPreviewProps } from \"./types\"\n\n/**\n * Renders two image tags, one with the preview image and one with the full\n * image. When the full image is loaded, the preview image is removed, revealing\n * the full image.\n */\nexport const ImageWithPreview = <T extends React.ElementType = \"img\">({\n as,\n preview,\n ...props\n}: ImageWithPreviewProps<T>) => {\n const [loaded, setLoaded] = useState(false)\n const ref = useRef<HTMLImageElement>(null)\n\n const onLoad = () => {\n setLoaded(true)\n }\n\n useEffect(() => {\n if (ref.current?.complete) {\n onLoad()\n }\n })\n\n const Img = as || \"img\"\n\n return (\n <>\n {!loaded && (\n <Img\n alt={props.alt}\n className={props.className}\n data-lqip\n height={props.height}\n id={props.id}\n src={preview}\n style={props.style}\n width={props.width}\n />\n )}\n <Img\n data-loading={loaded ? null : true}\n onLoad={onLoad}\n ref={ref}\n style={\n loaded\n ? undefined\n : {\n // must be > 4px to be lazy loaded\n height: \"10px !important\",\n\n // must be > 4px to be lazy loaded\n opacity: 0,\n\n pointerEvents: \"none\",\n // Cannot use negative x or y values, visibility: hidden, or display: none\n // to hide or the image might not get loaded.\n position: \"absolute\",\n userSelect: \"none\",\n width: \"10px !important\",\n zIndex: -10,\n }\n }\n {...props}\n />\n </>\n )\n}\n", "import React, {\n type ReactElement,\n type ElementType,\n type ComponentPropsWithoutRef,\n} from \"react\"\nimport type { PolymorphicComponentProps, SanityImageProps } from \"./types\"\nimport { buildSrc, buildSrcSet, buildSvgAttributes } from \"./urlBuilder\"\nimport { ImageWithPreview } from \"./ImageWithPreview\"\n\nexport const SanityImage = <C extends ElementType = \"img\">({\n as: component,\n\n // Sanity url\n baseUrl,\n projectId,\n dataset,\n\n // Image definition data\n id,\n hotspot,\n crop,\n width,\n height,\n mode = \"contain\",\n\n // Data for LQIP (preview image)\n preview,\n\n // Native-behavior overrides\n htmlWidth,\n htmlHeight,\n htmlId,\n\n // Image query string params\n queryParams,\n\n // Any remaining props are passed through to the rendered component\n ...rest\n}: PolymorphicComponentProps<C, SanityImageProps>): ReactElement => {\n if (!id) throw new Error(\"Missing required `id` prop for <SanityImage>.\")\n if (!baseUrl && (!projectId || !dataset))\n throw new Error(\n \"Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.\"\n )\n\n baseUrl = baseUrl ?? `https://cdn.sanity.io/images/${projectId}/${dataset}/`\n\n const isSvg = id.endsWith(\"-svg\")\n\n const ImageComponent =\n preview && !isSvg ? ImageWithPreview : component ?? \"img\"\n\n const componentProps: ComponentPropsWithoutRef<typeof ImageComponent> = {\n alt: rest.alt ?? \"\",\n loading: rest.loading ?? \"lazy\",\n id: htmlId,\n ...rest,\n }\n\n if (isSvg) {\n // Sanity ignores all transformations for SVGs, so we can just render the\n // component without passing a query string and without doing anything for\n // the preview.\n return (\n <ImageComponent\n {...buildSvgAttributes({ id, baseUrl })}\n {...componentProps}\n />\n )\n }\n\n // Create default src and build srcSet\n const srcParams = {\n baseUrl,\n id,\n crop,\n hotspot,\n width,\n height,\n mode,\n queryParams,\n }\n\n const { src, ...outputDimensions } = buildSrc(srcParams)\n componentProps.srcSet = buildSrcSet(srcParams).join(\", \")\n componentProps.src = src\n componentProps.width = htmlWidth ?? outputDimensions.width\n componentProps.height = htmlHeight ?? outputDimensions.height\n\n if (preview) {\n componentProps.as = component ?? \"img\"\n componentProps.preview = preview\n }\n\n return <ImageComponent {...componentProps} />\n}\n"],
|
|
5
|
+
"mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,sBAAAE,EAAA,gBAAAC,EAAA,aAAAC,EAAA,gBAAAC,EAAA,iBAAAC,IAAA,eAAAC,EAAAP,GCEO,IAAMQ,EAA0B,qCAQ1BC,EAAgBC,GAA6B,CACxD,IAAMC,EAAQH,EAAwB,KAAKE,CAAE,EACvC,CAAC,CAAEE,EAASC,EAAYC,CAAM,EAAIH,GAAA,KAAAA,EAAS,CAAC,EAElD,GAAI,CAACA,GAAS,CAACC,GAAW,CAACC,GAAc,CAACC,EACxC,MAAM,IAAI,MAAM,6BAA6BJ,IAAK,EAGpD,GAAM,CAACK,EAAOC,CAAM,EAAIH,EACrB,MAAM,GAAG,EACT,IAAKI,GAA0B,OAAO,SAASA,EAAO,EAAE,CAAC,EAE5D,GAAI,OAAO,MAAMF,CAAK,GAAK,OAAO,MAAMC,CAAM,GAAK,CAACD,GAAS,CAACC,EAC5D,MAAM,IAAI,MAAM,uBAAuBH,IAAa,EAGtD,MAAO,CACL,QAAAD,EACA,WAAY,CAAE,OAAAI,EAAQ,MAAAD,EAAO,YAAaA,EAAQC,CAAO,EACzD,OAAAF,CACF,CACF,EAUaI,EAAoBR,GAAuB,CAItD,IAAMS,EAAuBT,EAAG,YAAY,GAAG,EAE/C,OACEA,EAAG,MAAM,EAAGS,CAAoB,EAAI,IAAMT,EAAG,MAAMS,EAAuB,CAAC,CAE/E,ECrCO,IAAMC,EAAW,CAAC,CACvB,QAAAC,EACA,GAAGC,CACL,IAAyC,CACvC,GAAM,CAAE,SAAAC,EAAU,GAAGC,CAAY,EAAIC,EAAiB,CACpD,GAAGH,EACH,QAAS,CAAE,gBAAiB,EAAK,CACnC,CAAC,EAGD,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,+BAA+B,EAKjD,MAAO,CACL,IAAK,GAHU,GAAGF,IAAUK,EAAiBJ,EAAY,EAAE,OAGvCK,EAAiBH,CAAW,IAChD,MAAOD,EAAS,iBAAiB,MACjC,OAAQA,EAAS,iBAAiB,MACpC,CACF,EAEaK,EAAc,CAAC,CAC1B,GAAAC,EACA,KAAAC,EAAO,UACP,MAAAC,EACA,OAAAC,EACA,QAAAC,EACA,KAAAC,EACA,QAAAb,CACF,IAAsB,CAEpB,GAAM,CAAE,EAAAc,EAAG,EAAAC,CAAE,EAAIX,EAAiB,CAAE,GAAAI,EAAI,KAAAC,EAAM,MAAAC,EAAO,OAAAC,EAAQ,QAAAC,EAAS,KAAAC,CAAK,CAAC,EAGtEG,EAAW,GAAGhB,IAAUK,EAAiBG,CAAE,IAG3CS,EAAgBC,EAAmBJ,CAAC,EACvC,IAAKK,GAAa,CACjB,IAAMC,EAAgB,KAAK,MAAMN,EAAIK,CAAQ,EACvCE,EAAiBN,GAAK,KAAK,MAAMA,EAAII,CAAQ,EAGnD,GAAIA,EAAW,GAAKC,EAAgB,GAAI,OAAO,KAE/C,IAAME,EAA6ClB,EAAiB,CAClE,GAAAI,EACA,KAAAC,EACA,MAAOW,EACP,OAAQC,EACR,QAAAT,EACA,KAAAC,CACF,CAAC,EAED,MAAO,GAAGG,KAAYV,EAAiBgB,CAAM,KAAKA,EAAO,IAC3D,CAAC,EACA,OAAO,OAAO,EAEjB,OAAO,MAAM,KAAK,IAAI,IAAIL,CAAa,CAAC,CAC1C,EAEaM,EAAqB,CAAC,CAAE,GAAAf,EAAI,QAAAR,CAAQ,IAAsB,CACrE,GAAM,CAAE,QAAAwB,EAAS,WAAAC,EAAY,OAAAC,CAAO,EAAIC,EAAanB,CAAE,EAEvD,MAAO,CACL,IAAK,GAAGR,IAAUwB,KAAWC,EAAW,SAASA,EAAW,UAAUC,IACtE,MAAOD,EAAW,MAClB,OAAQA,EAAW,MACrB,CACF,EAEMP,EAAsBR,GAEtBA,EAAQ,IACH,CAAC,GAAK,EAAG,CAAC,EAIfA,EAAQ,IACH,CAAC,GAAK,EAAG,IAAK,CAAC,EAIpBA,EAAQ,KACH,CAAC,IAAM,GAAK,IAAM,EAAG,IAAK,CAAC,EAI7B,CAAC,IAAM,GAAK,IAAM,EAAG,KAAM,IAAK,KAAM,CAAC,EAMnCN,EAAmB,CAAC,CAC/B,GAAAI,EACA,KAAAC,EAAO,UACP,MAAAC,EACA,OAAAC,EACA,QAAAC,EACA,KAAAC,EACA,YAAAV,EACA,QAAS,CAAE,gBAAAyB,EAAkB,EAAM,EAAI,CAAC,CAC1C,IAKwB,CACtB,IAAMC,EAAmBF,EAAanB,CAAE,EAAE,WAGpC,CACJ,MAAOsB,EACP,OAAQC,EACR,YAAaC,CACf,EAAInB,EAAOoB,EAAiBJ,EAAkBhB,CAAI,EAAIgB,EAkCtD,GA/BKnB,IACCC,GAEFD,EAAQ,KAAK,MAAMC,EAASqB,CAAiB,EAG7CrB,EAAS,QAGTD,EAAQ,KAAK,MAAMoB,EAAW,CAAC,GASjCrB,IAAS,UACR,CAACC,GAAS,CAACC,GAAUD,EAAQC,IAAWqB,GAEzCvB,EAAO,UACEA,IAAS,WAAaE,IAI/BD,EAAQ,KAAK,IAAIA,EAAO,KAAK,MAAMC,EAASqB,CAAiB,CAAC,EAC9DrB,EAAS,QAIPD,EAAQoB,GAAanB,GAAUA,EAASoB,EAAY,CACtD,IAAMG,EAAuBvB,EAASD,EAAQC,EAASqB,EAEnDE,GAAwBF,GAE1BtB,EAAQoB,EACRnB,EAASA,GAAU,KAAK,MAAMD,EAAQwB,CAAoB,IAG1DvB,EAASoB,EACTrB,EAAQ,KAAK,MAAMC,EAASuB,CAAoB,GAQpD,IAAMZ,EAAoC,CACxC,EAAGZ,EACH,EAAG,GACH,GAAGP,CACL,EAUA,GAPKmB,EAAO,KAAIA,EAAO,KAAO,UAE1BT,IAEFS,EAAO,KAAOa,EAAUN,EAAkBhB,CAAI,GAG5CJ,IAAS,QAOX,GANAa,EAAO,IAAM,OAETX,IACFW,EAAO,EAAIX,GAGTC,EAAS,CAGX,IAAMwB,EAAIvB,EAAOD,EAAQ,GAAK,EAAIC,EAAK,KAAOA,EAAK,OAASD,EAAQ,EAC9DyB,EAAIxB,EAAOD,EAAQ,GAAK,EAAIC,EAAK,IAAMA,EAAK,QAAUD,EAAQ,EAEpEU,EAAO,MAAM,EAAIgB,EAAmBC,EAAMH,EAAG,EAAG,CAAC,EAAG,CAAC,EACrDd,EAAO,MAAM,EAAIgB,EAAmBC,EAAMF,EAAG,EAAG,CAAC,EAAG,CAAC,OAGrDf,EAAO,KAAO,eAGhBA,EAAO,IAAM,MAGf,GAAIM,EAAiB,CAEnB,IAAMY,EAAe7B,GAAU,KAAK,MAAMD,EAAQsB,CAAiB,EAEnEV,EAAO,SAAW,CAChB,iBAAAO,EACA,iBAAkB,CAChB,MAAAnB,EACA,OAAQ8B,EACR,YAAa9B,EAAQ8B,CACvB,CACF,EAGF,OAAyBlB,CAC3B,EAEMiB,EAAQ,CAACE,EAAeC,EAAaC,IACzC,KAAK,IAAID,EAAK,KAAK,IAAIC,EAAKF,CAAK,CAAC,EAC9BH,EAAqB,CAACG,EAAeG,IACzC,KAAK,MAAMH,EAAQ,KAAK,IAAI,GAAIG,CAAS,CAAC,EAAI,KAAK,IAAI,GAAIA,CAAS,EAEzDX,EAAmB,CAE9BR,EACAZ,IAC+B,CAC/B,GAAIA,EAAK,KAAOA,EAAK,OAAS,GAAKA,EAAK,IAAMA,EAAK,QAAU,EAC3D,MAAM,IAAI,MACR,iBAAiB,KAAK,UAAUA,CAAI,oCACtC,EAGF,IAAMH,EAAQ,KAAK,MAAMe,EAAW,OAAS,EAAIZ,EAAK,KAAOA,EAAK,MAAM,EAClEF,EAAS,KAAK,MAAMc,EAAW,QAAU,EAAIZ,EAAK,IAAMA,EAAK,OAAO,EACpEgC,EAAcnC,EAAQC,EAE5B,MAAO,CAAE,MAAAD,EAAO,OAAAC,EAAQ,YAAAkC,CAAY,CACtC,EAKaV,EAAY,CAEvBV,EACAZ,IACG,CACH,GAAM,CAAE,MAAAH,EAAO,OAAAC,CAAO,EAAIsB,EAAiBR,EAAYZ,CAAI,EAE3D,MAAO,CACL,KAAK,MAAMA,EAAK,KAAOY,EAAW,KAAK,EACvC,KAAK,MAAMZ,EAAK,IAAMY,EAAW,MAAM,EACvCf,EACAC,CACF,EAAE,KAAK,GAAG,CACZ,EAEaL,EAAoBgB,GAC/B,OAAO,QAAQA,CAAM,EAClB,KAAK,CAAC,CAACwB,CAAC,EAAG,CAACC,CAAC,IAAMD,EAAE,cAAcC,CAAC,CAAC,EACrC,IACC,CAAC,CAACC,EAAKP,CAAK,IACV,GAAG,mBAAmBO,CAAG,KAAK,mBAAmBP,CAAK,GAC1D,EACC,KAAK,GAAG,EACR,QAAQ,OAAQ,GAAG,EC9RxB,IAAAQ,EAAmD,oBAQtCC,EAAmB,CAAsC,CACpE,GAAAC,EACA,QAAAC,EACA,GAAGC,CACL,IAAgC,CAC9B,GAAM,CAACC,EAAQC,CAAS,KAAI,YAAS,EAAK,EACpCC,KAAM,UAAyB,IAAI,EAEnCC,EAAS,IAAM,CACnBF,EAAU,EAAI,CAChB,KAEA,aAAU,IAAM,CApBlB,IAAAG,GAqBQA,EAAAF,EAAI,UAAJ,MAAAE,EAAa,UACfD,EAAO,CAEX,CAAC,EAED,IAAME,EAAMR,GAAM,MAElB,OACE,EAAAS,QAAA,gBAAAA,QAAA,cACG,CAACN,GACA,EAAAM,QAAA,cAACD,EAAA,CACC,IAAKN,EAAM,IACX,UAAWA,EAAM,UACjB,YAAS,GACT,OAAQA,EAAM,OACd,GAAIA,EAAM,GACV,IAAKD,EACL,MAAOC,EAAM,MACb,MAAOA,EAAM,MACf,EAEF,EAAAO,QAAA,cAACD,EAAA,CACC,eAAcL,EAAS,KAAO,GAC9B,OAAQG,EACR,IAAKD,EACL,MACEF,EACI,OACA,CAEE,OAAQ,kBAGR,QAAS,EAET,cAAe,OAGf,SAAU,WACV,WAAY,OACZ,MAAO,kBACP,OAAQ,GACV,EAEL,GAAGD,EACN,CACF,CAEJ,ECrEA,IAAAQ,EAIO,oBAKA,IAAMC,EAAc,CAAgC,CACzD,GAAIC,EAGJ,QAAAC,EACA,UAAAC,EACA,QAAAC,EAGA,GAAAC,EACA,QAAAC,EACA,KAAAC,EACA,MAAAC,EACA,OAAAC,EACA,KAAAC,EAAO,UAGP,QAAAC,EAGA,UAAAC,EACA,WAAAC,EACA,OAAAC,EAGA,YAAAC,EAGA,GAAGC,CACL,IAAoE,CAtCpE,IAAAC,EAAAC,EAuCE,GAAI,CAACb,EAAI,MAAM,IAAI,MAAM,+CAA+C,EACxE,GAAI,CAACH,IAAY,CAACC,GAAa,CAACC,GAC9B,MAAM,IAAI,MACR,kFACF,EAEFF,EAAUA,GAAA,KAAAA,EAAW,gCAAgCC,KAAaC,KAElE,IAAMe,EAAQd,EAAG,SAAS,MAAM,EAE1Be,EACJT,GAAW,CAACQ,EAAQE,EAAmBpB,GAAA,KAAAA,EAAa,MAEhDqB,EAAkE,CACtE,KAAKL,EAAAD,EAAK,MAAL,KAAAC,EAAY,GACjB,SAASC,EAAAF,EAAK,UAAL,KAAAE,EAAgB,OACzB,GAAIJ,EACJ,GAAGE,CACL,EAEA,GAAIG,EAIF,OACE,EAAAI,QAAA,cAACH,EAAA,CACE,GAAGI,EAAmB,CAAE,GAAAnB,EAAI,QAAAH,CAAQ,CAAC,EACrC,GAAGoB,EACN,EAKJ,IAAMG,EAAY,CAChB,QAAAvB,EACA,GAAAG,EACA,KAAAE,EACA,QAAAD,EACA,MAAAE,EACA,OAAAC,EACA,KAAAC,EACA,YAAAK,CACF,EAEM,CAAE,IAAAW,EAAK,GAAGC,CAAiB,EAAIC,EAASH,CAAS,EACvD,OAAAH,EAAe,OAASO,EAAYJ,CAAS,EAAE,KAAK,IAAI,EACxDH,EAAe,IAAMI,EACrBJ,EAAe,MAAQV,GAAA,KAAAA,EAAae,EAAiB,MACrDL,EAAe,OAAST,GAAA,KAAAA,EAAcc,EAAiB,OAEnDhB,IACFW,EAAe,GAAKrB,GAAA,KAAAA,EAAa,MACjCqB,EAAe,QAAUX,GAGpB,EAAAY,QAAA,cAACH,EAAA,CAAgB,GAAGE,EAAgB,CAC7C",
|
|
6
|
+
"names": ["src_exports", "__export", "ImageWithPreview", "SanityImage", "buildSrc", "buildSrcSet", "parseImageId", "__toCommonJS", "SANITY_IMAGE_ID_PATTERN", "parseImageId", "id", "match", "assetId", "dimensions", "format", "width", "height", "value", "imageIdToUrlPath", "formatSeparatorIndex", "buildSrc", "baseUrl", "inputParams", "metadata", "queryParams", "buildQueryParams", "imageIdToUrlPath", "buildQueryString", "buildSrcSet", "id", "mode", "width", "height", "hotspot", "crop", "w", "h", "imageUrl", "srcSetEntries", "dynamicMultipliers", "multiple", "computedWidth", "computedHeight", "params", "buildSvgAttributes", "assetId", "dimensions", "format", "parseImageId", "includeMetadata", "sourceDimensions", "maxWidth", "maxHeight", "sourceAspectRatio", "croppedImageSize", "requestedAspectRatio", "buildRect", "x", "y", "roundWithPrecision", "clamp", "outputHeight", "value", "min", "max", "precision", "aspectRatio", "a", "b", "key", "import_react", "ImageWithPreview", "as", "preview", "props", "loaded", "setLoaded", "ref", "onLoad", "_a", "Img", "React", "import_react", "SanityImage", "component", "baseUrl", "projectId", "dataset", "id", "hotspot", "crop", "width", "height", "mode", "preview", "htmlWidth", "htmlHeight", "htmlId", "queryParams", "rest", "_a", "_b", "isSvg", "ImageComponent", "ImageWithPreview", "componentProps", "React", "buildSvgAttributes", "srcParams", "src", "outputDimensions", "buildSrc", "buildSrcSet"]
|
|
7
7
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { imageUrl, buildSource, buildSourceSet } from "./imageUrls";
|
|
1
|
+
export { buildSrc, buildSrcSet } from "./urlBuilder";
|
|
3
2
|
export { ImageWithPreview } from "./ImageWithPreview";
|
|
4
|
-
export {
|
|
3
|
+
export { parseImageId } from "./parseImageId";
|
|
5
4
|
export { SanityImage } from "./SanityImage";
|
package/dist/mjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
var A=/^image-([\da-f]+)-(\d+x\d+)-(\w+)$/,I=n=>{let t=A.exec(n),[,e,r,o]=t??[];if(!t||!e||!r||!o)throw new Error(`Could not parse image ID "${n}"`);let[a,s]=r.split("x").map(c=>Number.parseInt(c,10));if(Number.isNaN(a)||Number.isNaN(s)||!a||!s)throw new Error(`Invalid dimensions "${r}"`);return{assetId:e,dimensions:{height:s,width:a,aspectRatio:a/s},format:o}},x=n=>{let t=n.lastIndexOf("-");return n.slice(6,t)+"."+n.slice(t+1)};var P=({baseUrl:n,...t})=>{let{metadata:e,...r}=b({...t,options:{includeMetadata:!0}});if(!e)throw new Error("Missing image output metadata");return{src:`${`${n}${x(t.id)}`}?${T(r)}`,width:e.outputDimensions.width,height:e.outputDimensions.height}},S=({id:n,mode:t="contain",width:e,height:r,hotspot:o,crop:a,baseUrl:s})=>{let{w:c,h:p}=b({id:n,mode:t,width:e,height:r,hotspot:o,crop:a}),l=`${s}${x(n)}`,d=q(c).map(m=>{let i=Math.round(c*m),u=p&&Math.round(p*m);if(m<1&&i<50)return null;let f=b({id:n,mode:t,width:i,height:u,hotspot:o,crop:a});return`${l}?${T(f)} ${f.w}w`}).filter(Boolean);return Array.from(new Set(d))},D=({id:n,baseUrl:t})=>{let{assetId:e,dimensions:r,format:o}=I(n);return{src:`${t}${e}-${r.width}x${r.height}.${o}`,width:r.width,height:r.height}},q=n=>n<160?[.5,1,2]:n<750?[.5,1,1.5,2]:n<1400?[.25,.5,.75,1,1.5,2]:[.25,.5,.75,1,1.25,1.5,1.75,2],b=({id:n,mode:t="contain",width:e,height:r,hotspot:o,crop:a,queryParams:s,options:{includeMetadata:c=!1}={}})=>{let p=I(n).dimensions,{width:l,height:d,aspectRatio:m}=a?N(p,a):p;if(e||(r?(e=Math.round(r*m),r=void 0):e=Math.round(l/2)),t==="cover"&&(!e||!r||e/r===m)?t="contain":t==="contain"&&r&&(e=Math.min(e,Math.round(r*m)),r=void 0),e>l||r&&r>d){let u=r?e/r:m;u>=m?(e=l,r=r&&Math.round(e/u)):(r=d,e=Math.round(r*u))}let i={w:e,q:75,...s};if(i.fm||(i.auto="format"),a&&(i.rect=j(p,a)),t==="cover")if(i.fit="crop",r&&(i.h=r),o){let u=a?o.x/(1-a.left-a.right):o.x,f=a?o.y/(1-a.top-a.bottom):o.y;i["fp-x"]=R(v(u,0,1),3),i["fp-y"]=R(v(f,0,1),3)}else i.crop="entropy";else i.fit="max";if(c){let u=r||Math.round(e/m);i.metadata={sourceDimensions:p,outputDimensions:{width:e,height:u,aspectRatio:e/u}}}return i},v=(n,t,e)=>Math.max(t,Math.min(e,n)),R=(n,t)=>Math.round(n*Math.pow(10,t))/Math.pow(10,t),N=(n,t)=>{if(t.left+t.right>=1||t.top+t.bottom>=1)throw new Error(`Invalid crop: ${JSON.stringify(t)}; crop values must be less than 1`);let e=Math.round(n.width*(1-t.left-t.right)),r=Math.round(n.height*(1-t.top-t.bottom)),o=e/r;return{width:e,height:r,aspectRatio:o}},j=(n,t)=>{let{width:e,height:r}=N(n,t);return[Math.round(t.left*n.width),Math.round(t.top*n.height),e,r].join(",")},T=n=>Object.entries(n).sort(([t],[e])=>t.localeCompare(e)).map(([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`).join("&").replace(/%2C/g,",");import h,{useEffect as H,useRef as L,useState as O}from"react";var w=({as:n,preview:t,...e})=>{let[r,o]=O(!1),a=L(null),s=()=>{o(!0)};H(()=>{a.current?.complete&&s()});let c=n||"img";return h.createElement(h.Fragment,null,!r&&h.createElement(c,{alt:e.alt,className:e.className,"data-lqip":!0,height:e.height,id:e.id,src:t,style:e.style,width:e.width}),h.createElement(c,{"data-loading":r?null:!0,onLoad:s,ref:a,style:r?void 0:{height:"10px !important",opacity:0,pointerEvents:"none",position:"absolute",userSelect:"none",width:"10px !important",zIndex:-10},...e}))};import W from"react";var U=({as:n,baseUrl:t,projectId:e,dataset:r,id:o,hotspot:a,crop:s,width:c,height:p,mode:l="contain",preview:d,htmlWidth:m,htmlHeight:i,htmlId:u,queryParams:f,...y})=>{if(!o)throw new Error("Missing required `id` prop for <SanityImage>.");if(!t&&(!e||!r))throw new Error("Missing required `baseUrl` or `projectId` and `dataset` props for <SanityImage>.");t=t??`https://cdn.sanity.io/images/${e}/${r}/`;let M=o.endsWith("-svg"),$=d&&!M?w:n??"img",g={alt:y.alt??"",loading:y.loading??"lazy",id:u,...y};if(M)return W.createElement($,{...D({id:o,baseUrl:t}),...g});let C={baseUrl:t,id:o,crop:s,hotspot:a,width:c,height:p,mode:l,queryParams:f},{src:Q,...E}=P(C);return g.srcSet=S(C).join(", "),g.src=Q,g.width=m??E.width,g.height=i??E.height,d&&(g.as=n??"img",g.preview=d),W.createElement($,{...g})};export{w as ImageWithPreview,U as SanityImage,P as buildSrc,S as buildSrcSet,I as parseImageId};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|