@nonphoto/sanity-image 2.0.1 → 3.0.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/dist/index.d.ts CHANGED
@@ -1,58 +1,63 @@
1
- import { SanityImageObject, SanityImageDimensions, SanityImageCrop, SanityImageHotspot, SanityReference, SanityAsset, SanityClientLike, SanityProjectDetails, SanityModernClientLike, SanityImageSource } from '@sanity/image-url/lib/types/types.js';
2
-
3
- interface SanityImageObjectLike extends Pick<SanityImageObject, "asset"> {
4
- [key: string]: any;
5
- }
6
- interface SanityImageMetadata {
7
- lqip?: string;
8
- dimensions?: SanityImageDimensions;
9
- [key: string]: any;
10
- }
11
- interface Size {
12
- width: number;
13
- height: number;
14
- }
15
- declare const defaultWidths: number[];
16
- declare const defaultWidth = 1280;
17
- declare const defaultQuality = 90;
18
- declare const fitComparators: {
19
- cover: (...values: number[]) => number;
20
- contain: (...values: number[]) => number;
1
+ type ImageFormat = "jpg" | "pjpg" | "png" | "webp";
2
+ type FitMode = "clip" | "crop" | "fill" | "fillmax" | "max" | "scale" | "min";
3
+ type CropMode = "top" | "bottom" | "left" | "right" | "center" | "focalpoint" | "entropy";
4
+ type AutoMode = "format";
5
+ type Orientation = 0 | 90 | 180 | 270;
6
+ type Dpr = 1 | 2 | 3;
7
+ type SanityImageParams = {
8
+ auto?: AutoMode;
9
+ background?: string;
10
+ blur?: number;
11
+ crop?: CropMode;
12
+ download?: boolean | string;
13
+ dpr?: Dpr;
14
+ fit?: FitMode;
15
+ flipHorizontal?: boolean;
16
+ flipVertical?: boolean;
17
+ focalPoint?: {
18
+ x: number;
19
+ y: number;
20
+ };
21
+ format?: ImageFormat;
22
+ frame?: number;
23
+ height?: number;
24
+ invert?: boolean;
25
+ maxHeight?: number;
26
+ maxWidth?: number;
27
+ minHeight?: number;
28
+ minWidth?: number;
29
+ orientation?: Orientation;
30
+ pad?: number;
31
+ quality?: number;
32
+ rect?: {
33
+ left: number;
34
+ top: number;
35
+ width: number;
36
+ height: number;
37
+ };
38
+ saturation?: number;
39
+ sharpen?: number;
40
+ width?: number;
21
41
  };
22
- declare function fit(containee: Size, container: Size, mode: keyof typeof fitComparators): {
42
+ declare function sanityImageParamsToSearchParamEntries({ auto, background, blur, crop, download, dpr, fit, flipHorizontal, flipVertical, focalPoint, format, frame, height, invert, maxHeight, maxWidth, minHeight, minWidth, orientation, pad, quality, rect, saturation, sharpen, width, }: SanityImageParams): string[][];
43
+
44
+ interface SanityImageAssetStub {
45
+ id: string;
23
46
  width: number;
24
47
  height: number;
25
- };
26
- declare function isCrop(x: unknown): x is SanityImageCrop;
27
- declare function isHotspot(x: unknown): x is SanityImageHotspot;
28
- declare function isImageObjectLike(x: unknown): x is SanityImageObjectLike;
29
- declare function isImageObject(x: unknown): x is SanityImageObject;
30
- declare function isReference(x: unknown): x is SanityReference;
31
- declare function isAsset(x: unknown): x is SanityAsset;
32
- declare function isDimensions(x: unknown): x is SanityImageDimensions;
33
- declare function isSize(x: unknown): x is Size;
34
- declare function isMetadata(x: unknown): x is SanityImageMetadata;
35
- interface ImageSrcOptions {
36
- client: SanityClientLike | SanityProjectDetails | SanityModernClientLike;
37
- source: SanityImageSource;
38
- width: number;
39
- quality?: number;
40
- aspectRatio?: number;
48
+ format: string;
49
+ vanityName?: string;
41
50
  }
42
- declare function imageSrc({ client, source, width, quality, aspectRatio, }: ImageSrcOptions): string;
43
- interface ImageSrcsetOptions {
44
- client: SanityClientLike | SanityProjectDetails | SanityModernClientLike;
45
- source: SanityImageSource;
46
- widths?: number[];
47
- quality?: number;
48
- aspectRatio?: number;
51
+ declare function parseSanityImageAssetId(assetId: string): SanityImageAssetStub | undefined;
52
+ declare function sanityImageAssetStub(source: unknown): SanityImageAssetStub | undefined;
53
+ declare function sanityImageAssetId(source: unknown): string | undefined;
54
+
55
+ declare const defaultSrcsetWidths: number[];
56
+ interface SanityClientLike {
57
+ projectId: string;
58
+ dataset: string;
49
59
  }
50
- declare function imageSrcset({ client, source, widths, quality, aspectRatio, }: ImageSrcsetOptions): string;
51
- declare function imageAlt(source: SanityImageSource): string | undefined;
52
- declare function croppedSize(intrinsicSize: Size, crop: SanityImageCrop): Size;
53
- declare function aspectRatio(size: Size): number;
54
- declare function imageMetadata(source: SanityImageSource): SanityImageMetadata | undefined;
55
- declare function imageCroppedSize(source: SanityImageSource): Size | undefined;
56
- declare function imageAspectRatio(source: SanityImageSource): number | undefined;
60
+ declare function sanityImageUrl(client: SanityClientLike, image: SanityImageAssetStub, params?: SanityImageParams): string;
61
+ declare function sanityImageSrcset(client: SanityClientLike, image: SanityImageAssetStub, params?: Omit<SanityImageParams, "width">, widths?: number[]): string;
57
62
 
58
- export { type ImageSrcOptions, type ImageSrcsetOptions, type SanityImageMetadata, type SanityImageObjectLike, type Size, aspectRatio, croppedSize, defaultQuality, defaultWidth, defaultWidths, fit, fitComparators, imageAlt, imageAspectRatio, imageCroppedSize, imageMetadata, imageSrc, imageSrcset, isAsset, isCrop, isDimensions, isHotspot, isImageObject, isImageObjectLike, isMetadata, isReference, isSize };
63
+ export { type AutoMode, type CropMode, type Dpr, type FitMode, type ImageFormat, type Orientation, type SanityClientLike, type SanityImageAssetStub, type SanityImageParams, defaultSrcsetWidths, parseSanityImageAssetId, sanityImageAssetId, sanityImageAssetStub, sanityImageParamsToSearchParamEntries, sanityImageSrcset, sanityImageUrl };
package/dist/index.js CHANGED
@@ -1,6 +1,83 @@
1
- // src/index.ts
2
- import imageUrlBuilder from "@sanity/image-url";
3
- var defaultWidths = [
1
+ // src/params.ts
2
+ function sanityImageParamsToSearchParamEntries({
3
+ auto,
4
+ background,
5
+ blur,
6
+ crop,
7
+ download,
8
+ dpr,
9
+ fit,
10
+ flipHorizontal,
11
+ flipVertical,
12
+ focalPoint,
13
+ format,
14
+ frame,
15
+ height,
16
+ invert,
17
+ maxHeight,
18
+ maxWidth,
19
+ minHeight,
20
+ minWidth,
21
+ orientation,
22
+ pad,
23
+ quality,
24
+ rect,
25
+ saturation,
26
+ sharpen,
27
+ width
28
+ }) {
29
+ return Object.entries({
30
+ auto,
31
+ bg: background,
32
+ blur,
33
+ crop,
34
+ dl: download,
35
+ dpr,
36
+ fit,
37
+ flip: [flipHorizontal && "h", flipVertical && "v"].filter(Boolean).join(""),
38
+ fm: format,
39
+ "fp-x": focalPoint?.x,
40
+ "fp-y": focalPoint?.y,
41
+ frame,
42
+ h: height,
43
+ invert,
44
+ "max-h": maxHeight,
45
+ "max-w": maxWidth,
46
+ "min-h": minHeight,
47
+ "min-w": minWidth,
48
+ or: orientation,
49
+ pad,
50
+ q: quality,
51
+ rect: rect ? [rect.left, rect.top, rect.width, rect.height].map(Math.round).join(",") : void 0,
52
+ sat: saturation,
53
+ sharp: sharpen,
54
+ w: width
55
+ }).filter(([, value]) => typeof value === "number" || Boolean(value)).map(([key, value]) => [
56
+ key,
57
+ encodeURIComponent(
58
+ typeof value === "number" ? Math.round(value) : value
59
+ )
60
+ ]);
61
+ }
62
+
63
+ // src/stub.ts
64
+ function parseSanityImageAssetId(assetId) {
65
+ const matches = assetId.match(/^image-(\w+)-(\d+)x(\d+)-(\w+)$/);
66
+ if (matches) {
67
+ const [, id, width, height, format] = matches;
68
+ return { id, width: Number(width), height: Number(height), format };
69
+ }
70
+ }
71
+ function sanityImageAssetStub(source) {
72
+ const id = sanityImageAssetId(source);
73
+ return id ? parseSanityImageAssetId(id) : void 0;
74
+ }
75
+ function sanityImageAssetId(source) {
76
+ return typeof source === "string" ? source : typeof source === "object" && source != null ? "_ref" in source && typeof source._ref === "string" ? source._ref : "_id" in source && typeof source._id === "string" ? source._id : "asset" in source ? sanityImageAssetId(source.asset) : void 0 : void 0;
77
+ }
78
+
79
+ // src/url.ts
80
+ var defaultSrcsetWidths = [
4
81
  6016,
5
82
  // 6K
6
83
  5120,
@@ -32,116 +109,43 @@ var defaultWidths = [
32
109
  360,
33
110
  240
34
111
  ];
35
- var defaultWidth = 1280;
36
- var defaultQuality = 90;
37
- var fitComparators = {
38
- cover: Math.max,
39
- contain: Math.min
40
- };
41
- function fit(containee, container, mode) {
42
- const sx = container.width / containee.width;
43
- const sy = container.height / containee.height;
44
- const s = fitComparators[mode](sx, sy);
45
- return {
46
- width: containee.width * s,
47
- height: containee.height * s
48
- };
49
- }
50
- function isCrop(x) {
51
- return x != null && typeof x == "object" && "top" in x && typeof x.top === "number" && "right" in x && typeof x.right === "number" && "bottom" in x && typeof x.bottom === "number" && "left" in x && typeof x.left === "number" && ("_type" in x ? typeof x._type === "undefined" || typeof x._type === "string" : true);
52
- }
53
- function isHotspot(x) {
54
- return x != null && typeof x == "object" && "width" in x && typeof x.width === "number" && "height" in x && typeof x.height === "number" && "x" in x && typeof x.x === "number" && "y" in x && typeof x.y === "number" && ("_type" in x ? typeof x._type === "undefined" || typeof x._type === "string" : true);
55
- }
56
- function isImageObjectLike(x) {
57
- return x != null && typeof x === "object" && "asset" in x && (isAsset(x.asset) || isReference(x.asset));
58
- }
59
- function isImageObject(x) {
60
- return isImageObjectLike(x) && ("crop" in x ? typeof x.crop === "undefined" || isCrop(x.crop) : true) && ("hotspot" in x ? typeof x.hotspot === "undefined" || isHotspot(x.hotspot) : true);
61
- }
62
- function isReference(x) {
63
- return x != null && typeof x === "object" && "_ref" in x && typeof x._ref === "string";
64
- }
65
- function isAsset(x) {
66
- return x != null && typeof x === "object" && "_type" in x && x._type === "sanity.asset";
67
- }
68
- function isDimensions(x) {
69
- return x != null && typeof x === "object" && "aspectRatio" in x && typeof x.aspectRatio === "number" && "width" in x && typeof x.width === "number" && "height" in x && typeof x.height === "number";
70
- }
71
- function isSize(x) {
72
- return x != null && typeof x === "object" && "width" in x && typeof x.width === "number" && "height" in x && typeof x.height === "number";
73
- }
74
- function isMetadata(x) {
75
- return x != null && typeof x === "object" && "lqip" in x && typeof x.lqip === "string" && "dimensions" in x && isDimensions(x.dimensions);
76
- }
77
- function imageSrc({
78
- client,
79
- source,
80
- width,
81
- quality = defaultQuality,
82
- aspectRatio: aspectRatio2
83
- }) {
84
- const builder = imageUrlBuilder(client).image(source).quality(quality).auto("format").width(width);
85
- return (aspectRatio2 ? builder.height(width * aspectRatio2) : builder).url();
86
- }
87
- function imageSrcset({
88
- client,
89
- source,
90
- widths = defaultWidths,
91
- quality = defaultQuality,
92
- aspectRatio: aspectRatio2
93
- }) {
94
- const builder = imageUrlBuilder(client).image(source).quality(quality).auto("format");
95
- return widths.sort((a, b) => a - b).map((width) => {
96
- const url = (aspectRatio2 ? builder.height(width * aspectRatio2) : builder).width(width).url();
112
+ function sanityImageUrl(client, image, params) {
113
+ const url = new URL(
114
+ [
115
+ `https://cdn.sanity.io/images`,
116
+ client.projectId,
117
+ client.dataset,
118
+ `${image.id}-${image.width}x${image.height}.${image.format}`,
119
+ image.vanityName
120
+ ].filter(Boolean).join("/")
121
+ );
122
+ if (params) {
123
+ url.search = new URLSearchParams(
124
+ sanityImageParamsToSearchParamEntries(params)
125
+ ).toString();
126
+ }
127
+ return url.href;
128
+ }
129
+ function sanityImageSrcset(client, image, params, widths = defaultSrcsetWidths) {
130
+ const aspectRatio = image.height / image.width;
131
+ return [
132
+ ...widths.sort((a, b) => a - b).filter((width) => width < image.width),
133
+ image.width
134
+ ].map((width) => {
135
+ const url = sanityImageUrl(client, image, {
136
+ ...params,
137
+ width,
138
+ height: width * aspectRatio
139
+ });
97
140
  return `${url} ${width}w`;
98
141
  }).join(",");
99
142
  }
100
- function imageAlt(source) {
101
- return isImageObjectLike(source) && "alt" in source && typeof source.alt === "string" ? source.alt : void 0;
102
- }
103
- function croppedSize(intrinsicSize, crop) {
104
- return {
105
- width: intrinsicSize.width - crop.left - crop.right,
106
- height: intrinsicSize.height - crop.top - crop.bottom
107
- };
108
- }
109
- function aspectRatio(size) {
110
- return size.height / size.width;
111
- }
112
- function imageMetadata(source) {
113
- const asset = isImageObjectLike(source) ? source.asset : source;
114
- return isAsset(asset) && isMetadata(asset.metadata) ? asset.metadata : void 0;
115
- }
116
- function imageCroppedSize(source) {
117
- const metadata = imageMetadata(source);
118
- return metadata?.dimensions ? isImageObject(source) && source.crop != null ? croppedSize(metadata.dimensions, source.crop) : metadata.dimensions : void 0;
119
- }
120
- function imageAspectRatio(source) {
121
- const size = imageCroppedSize(source);
122
- return size ? aspectRatio(size) : void 0;
123
- }
124
143
  export {
125
- aspectRatio,
126
- croppedSize,
127
- defaultQuality,
128
- defaultWidth,
129
- defaultWidths,
130
- fit,
131
- fitComparators,
132
- imageAlt,
133
- imageAspectRatio,
134
- imageCroppedSize,
135
- imageMetadata,
136
- imageSrc,
137
- imageSrcset,
138
- isAsset,
139
- isCrop,
140
- isDimensions,
141
- isHotspot,
142
- isImageObject,
143
- isImageObjectLike,
144
- isMetadata,
145
- isReference,
146
- isSize
144
+ defaultSrcsetWidths,
145
+ parseSanityImageAssetId,
146
+ sanityImageAssetId,
147
+ sanityImageAssetStub,
148
+ sanityImageParamsToSearchParamEntries,
149
+ sanityImageSrcset,
150
+ sanityImageUrl
147
151
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonphoto/sanity-image",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "author": "Jonas Luebbers <jonas@jonasluebbers.com> (https://www.jonasluebbers.com)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,9 +28,6 @@
28
28
  "build": "tsup ./src/index.ts --dts --format esm --clean",
29
29
  "release": "semantic-release"
30
30
  },
31
- "dependencies": {
32
- "@sanity/image-url": "^1.1.0"
33
- },
34
31
  "devDependencies": {
35
32
  "semantic-release": "24.2.7",
36
33
  "tsup": "8.5.0",
package/src/index.ts CHANGED
@@ -1,278 +1,3 @@
1
- import imageUrlBuilder from "@sanity/image-url";
2
- import type {
3
- SanityAsset,
4
- SanityClientLike,
5
- SanityImageCrop,
6
- SanityImageDimensions,
7
- SanityImageHotspot,
8
- SanityImageObject,
9
- SanityImageSource,
10
- SanityModernClientLike,
11
- SanityProjectDetails,
12
- SanityReference,
13
- } from "@sanity/image-url/lib/types/types.js";
14
-
15
- export interface SanityImageObjectLike
16
- extends Pick<SanityImageObject, "asset"> {
17
- [key: string]: any;
18
- }
19
-
20
- export interface SanityImageMetadata {
21
- lqip?: string;
22
- dimensions?: SanityImageDimensions;
23
- [key: string]: any;
24
- }
25
-
26
- export interface Size {
27
- width: number;
28
- height: number;
29
- }
30
-
31
- export const defaultWidths = [
32
- 6016, // 6K
33
- 5120, // 5K
34
- 4480, // 4.5K
35
- 3840, // 4K
36
- 3200, // QHD+
37
- 2560, // WQXGA
38
- 2048, // QXGA
39
- 1920, // 1080p
40
- 1668, // iPad
41
- 1280, // 720p
42
- 1080, // iPhone 6-8 Plus
43
- 960,
44
- 720, // iPhone 6-8
45
- 640, // 480p
46
- 480,
47
- 360,
48
- 240,
49
- ];
50
-
51
- export const defaultWidth = 1280;
52
-
53
- export const defaultQuality = 90;
54
-
55
- export const fitComparators = {
56
- cover: Math.max,
57
- contain: Math.min,
58
- };
59
-
60
- // TODO: impement comparators for every FitMode
61
- export function fit(
62
- containee: Size,
63
- container: Size,
64
- mode: keyof typeof fitComparators
65
- ) {
66
- const sx = container.width / containee.width;
67
- const sy = container.height / containee.height;
68
- const s = fitComparators[mode](sx, sy);
69
- return {
70
- width: containee.width * s,
71
- height: containee.height * s,
72
- };
73
- }
74
-
75
- export function isCrop(x: unknown): x is SanityImageCrop {
76
- return (
77
- x != null &&
78
- typeof x == "object" &&
79
- "top" in x &&
80
- typeof x.top === "number" &&
81
- "right" in x &&
82
- typeof x.right === "number" &&
83
- "bottom" in x &&
84
- typeof x.bottom === "number" &&
85
- "left" in x &&
86
- typeof x.left === "number" &&
87
- ("_type" in x
88
- ? typeof x._type === "undefined" || typeof x._type === "string"
89
- : true)
90
- );
91
- }
92
-
93
- export function isHotspot(x: unknown): x is SanityImageHotspot {
94
- return (
95
- x != null &&
96
- typeof x == "object" &&
97
- "width" in x &&
98
- typeof x.width === "number" &&
99
- "height" in x &&
100
- typeof x.height === "number" &&
101
- "x" in x &&
102
- typeof x.x === "number" &&
103
- "y" in x &&
104
- typeof x.y === "number" &&
105
- ("_type" in x
106
- ? typeof x._type === "undefined" || typeof x._type === "string"
107
- : true)
108
- );
109
- }
110
-
111
- export function isImageObjectLike(x: unknown): x is SanityImageObjectLike {
112
- return (
113
- x != null &&
114
- typeof x === "object" &&
115
- "asset" in x &&
116
- (isAsset(x.asset) || isReference(x.asset))
117
- );
118
- }
119
-
120
- export function isImageObject(x: unknown): x is SanityImageObject {
121
- return (
122
- isImageObjectLike(x) &&
123
- ("crop" in x ? typeof x.crop === "undefined" || isCrop(x.crop) : true) &&
124
- ("hotspot" in x
125
- ? typeof x.hotspot === "undefined" || isHotspot(x.hotspot)
126
- : true)
127
- );
128
- }
129
-
130
- export function isReference(x: unknown): x is SanityReference {
131
- return (
132
- x != null &&
133
- typeof x === "object" &&
134
- "_ref" in x &&
135
- typeof x._ref === "string"
136
- );
137
- }
138
-
139
- export function isAsset(x: unknown): x is SanityAsset {
140
- return (
141
- x != null &&
142
- typeof x === "object" &&
143
- "_type" in x &&
144
- x._type === "sanity.asset"
145
- );
146
- }
147
-
148
- export function isDimensions(x: unknown): x is SanityImageDimensions {
149
- return (
150
- x != null &&
151
- typeof x === "object" &&
152
- "aspectRatio" in x &&
153
- typeof x.aspectRatio === "number" &&
154
- "width" in x &&
155
- typeof x.width === "number" &&
156
- "height" in x &&
157
- typeof x.height === "number"
158
- );
159
- }
160
-
161
- export function isSize(x: unknown): x is Size {
162
- return (
163
- x != null &&
164
- typeof x === "object" &&
165
- "width" in x &&
166
- typeof x.width === "number" &&
167
- "height" in x &&
168
- typeof x.height === "number"
169
- );
170
- }
171
-
172
- export function isMetadata(x: unknown): x is SanityImageMetadata {
173
- return (
174
- x != null &&
175
- typeof x === "object" &&
176
- "lqip" in x &&
177
- typeof x.lqip === "string" &&
178
- "dimensions" in x &&
179
- isDimensions(x.dimensions)
180
- );
181
- }
182
-
183
- export interface ImageSrcOptions {
184
- client: SanityClientLike | SanityProjectDetails | SanityModernClientLike;
185
- source: SanityImageSource;
186
- width: number;
187
- quality?: number;
188
- aspectRatio?: number;
189
- }
190
-
191
- export function imageSrc({
192
- client,
193
- source,
194
- width,
195
- quality = defaultQuality,
196
- aspectRatio,
197
- }: ImageSrcOptions): string {
198
- const builder = imageUrlBuilder(client)
199
- .image(source)
200
- .quality(quality)
201
- .auto("format")
202
- .width(width);
203
- return (aspectRatio ? builder.height(width * aspectRatio) : builder).url();
204
- }
205
-
206
- export interface ImageSrcsetOptions {
207
- client: SanityClientLike | SanityProjectDetails | SanityModernClientLike;
208
- source: SanityImageSource;
209
- widths?: number[];
210
- quality?: number;
211
- aspectRatio?: number;
212
- }
213
-
214
- export function imageSrcset({
215
- client,
216
- source,
217
- widths = defaultWidths,
218
- quality = defaultQuality,
219
- aspectRatio,
220
- }: ImageSrcsetOptions): string {
221
- const builder = imageUrlBuilder(client)
222
- .image(source)
223
- .quality(quality)
224
- .auto("format");
225
- return widths
226
- .sort((a, b) => a - b)
227
- .map((width) => {
228
- const url = (aspectRatio ? builder.height(width * aspectRatio) : builder)
229
- .width(width)
230
- .url();
231
- return `${url} ${width}w`;
232
- })
233
- .join(",");
234
- }
235
-
236
- export function imageAlt(source: SanityImageSource): string | undefined {
237
- return isImageObjectLike(source) &&
238
- "alt" in source &&
239
- typeof source.alt === "string"
240
- ? source.alt
241
- : undefined;
242
- }
243
-
244
- export function croppedSize(intrinsicSize: Size, crop: SanityImageCrop): Size {
245
- return {
246
- width: intrinsicSize.width - crop.left - crop.right,
247
- height: intrinsicSize.height - crop.top - crop.bottom,
248
- };
249
- }
250
-
251
- export function aspectRatio(size: Size): number {
252
- return size.height / size.width;
253
- }
254
-
255
- export function imageMetadata(
256
- source: SanityImageSource
257
- ): SanityImageMetadata | undefined {
258
- const asset = isImageObjectLike(source) ? source.asset : source;
259
- return isAsset(asset) && isMetadata(asset.metadata)
260
- ? asset.metadata
261
- : undefined;
262
- }
263
-
264
- export function imageCroppedSize(source: SanityImageSource): Size | undefined {
265
- const metadata = imageMetadata(source);
266
- return metadata?.dimensions
267
- ? isImageObject(source) && source.crop != null
268
- ? croppedSize(metadata.dimensions, source.crop)
269
- : metadata.dimensions
270
- : undefined;
271
- }
272
-
273
- export function imageAspectRatio(
274
- source: SanityImageSource
275
- ): number | undefined {
276
- const size = imageCroppedSize(source);
277
- return size ? aspectRatio(size) : undefined;
278
- }
1
+ export * from "./params";
2
+ export * from "./stub";
3
+ export * from "./url";
package/src/params.ts ADDED
@@ -0,0 +1,118 @@
1
+ export type ImageFormat = "jpg" | "pjpg" | "png" | "webp";
2
+
3
+ export type FitMode =
4
+ | "clip"
5
+ | "crop"
6
+ | "fill"
7
+ | "fillmax"
8
+ | "max"
9
+ | "scale"
10
+ | "min";
11
+
12
+ export type CropMode =
13
+ | "top"
14
+ | "bottom"
15
+ | "left"
16
+ | "right"
17
+ | "center"
18
+ | "focalpoint"
19
+ | "entropy";
20
+
21
+ export type AutoMode = "format";
22
+
23
+ export type Orientation = 0 | 90 | 180 | 270;
24
+
25
+ export type Dpr = 1 | 2 | 3;
26
+
27
+ export type SanityImageParams = {
28
+ auto?: AutoMode;
29
+ background?: string;
30
+ blur?: number;
31
+ crop?: CropMode;
32
+ download?: boolean | string;
33
+ dpr?: Dpr;
34
+ fit?: FitMode;
35
+ flipHorizontal?: boolean;
36
+ flipVertical?: boolean;
37
+ focalPoint?: { x: number; y: number };
38
+ format?: ImageFormat;
39
+ frame?: number;
40
+ height?: number;
41
+ invert?: boolean;
42
+ maxHeight?: number;
43
+ maxWidth?: number;
44
+ minHeight?: number;
45
+ minWidth?: number;
46
+ orientation?: Orientation;
47
+ pad?: number;
48
+ quality?: number;
49
+ rect?: { left: number; top: number; width: number; height: number };
50
+ saturation?: number;
51
+ sharpen?: number;
52
+ width?: number;
53
+ };
54
+
55
+ export function sanityImageParamsToSearchParamEntries({
56
+ auto,
57
+ background,
58
+ blur,
59
+ crop,
60
+ download,
61
+ dpr,
62
+ fit,
63
+ flipHorizontal,
64
+ flipVertical,
65
+ focalPoint,
66
+ format,
67
+ frame,
68
+ height,
69
+ invert,
70
+ maxHeight,
71
+ maxWidth,
72
+ minHeight,
73
+ minWidth,
74
+ orientation,
75
+ pad,
76
+ quality,
77
+ rect,
78
+ saturation,
79
+ sharpen,
80
+ width,
81
+ }: SanityImageParams): string[][] {
82
+ return Object.entries({
83
+ auto,
84
+ bg: background,
85
+ blur,
86
+ crop,
87
+ dl: download,
88
+ dpr,
89
+ fit,
90
+ flip: [flipHorizontal && "h", flipVertical && "v"].filter(Boolean).join(""),
91
+ fm: format,
92
+ "fp-x": focalPoint?.x,
93
+ "fp-y": focalPoint?.y,
94
+ frame,
95
+ h: height,
96
+ invert,
97
+ "max-h": maxHeight,
98
+ "max-w": maxWidth,
99
+ "min-h": minHeight,
100
+ "min-w": minWidth,
101
+ or: orientation,
102
+ pad,
103
+ q: quality,
104
+ rect: rect
105
+ ? [rect.left, rect.top, rect.width, rect.height].map(Math.round).join(",")
106
+ : undefined,
107
+ sat: saturation,
108
+ sharp: sharpen,
109
+ w: width,
110
+ })
111
+ .filter(([, value]) => typeof value === "number" || Boolean(value))
112
+ .map(([key, value]) => [
113
+ key,
114
+ encodeURIComponent(
115
+ typeof value === "number" ? Math.round(value) : (value as string)
116
+ ),
117
+ ]);
118
+ }
package/src/stub.ts ADDED
@@ -0,0 +1,38 @@
1
+ export interface SanityImageAssetStub {
2
+ id: string;
3
+ width: number;
4
+ height: number;
5
+ format: string;
6
+ vanityName?: string;
7
+ }
8
+
9
+ export function parseSanityImageAssetId(
10
+ assetId: string
11
+ ): SanityImageAssetStub | undefined {
12
+ const matches = assetId.match(/^image-(\w+)-(\d+)x(\d+)-(\w+)$/);
13
+ if (matches) {
14
+ const [, id, width, height, format] = matches;
15
+ return { id, width: Number(width), height: Number(height), format };
16
+ }
17
+ }
18
+
19
+ export function sanityImageAssetStub(
20
+ source: unknown
21
+ ): SanityImageAssetStub | undefined {
22
+ const id = sanityImageAssetId(source);
23
+ return id ? parseSanityImageAssetId(id) : undefined;
24
+ }
25
+
26
+ export function sanityImageAssetId(source: unknown): string | undefined {
27
+ return typeof source === "string"
28
+ ? source
29
+ : typeof source === "object" && source != null
30
+ ? "_ref" in source && typeof source._ref === "string"
31
+ ? source._ref
32
+ : "_id" in source && typeof source._id === "string"
33
+ ? source._id
34
+ : "asset" in source
35
+ ? sanityImageAssetId(source.asset)
36
+ : undefined
37
+ : undefined;
38
+ }
package/src/url.ts ADDED
@@ -0,0 +1,76 @@
1
+ import {
2
+ SanityImageParams,
3
+ sanityImageParamsToSearchParamEntries,
4
+ } from "./params";
5
+ import { SanityImageAssetStub } from "./stub";
6
+
7
+ export const defaultSrcsetWidths = [
8
+ 6016, // 6K
9
+ 5120, // 5K
10
+ 4480, // 4.5K
11
+ 3840, // 4K
12
+ 3200, // QHD+
13
+ 2560, // WQXGA
14
+ 2048, // QXGA
15
+ 1920, // 1080p
16
+ 1668, // iPad
17
+ 1280, // 720p
18
+ 1080, // iPhone 6-8 Plus
19
+ 960,
20
+ 720, // iPhone 6-8
21
+ 640, // 480p
22
+ 480,
23
+ 360,
24
+ 240,
25
+ ];
26
+
27
+ export interface SanityClientLike {
28
+ projectId: string;
29
+ dataset: string;
30
+ }
31
+
32
+ export function sanityImageUrl(
33
+ client: SanityClientLike,
34
+ image: SanityImageAssetStub,
35
+ params?: SanityImageParams
36
+ ): string {
37
+ const url = new URL(
38
+ [
39
+ `https://cdn.sanity.io/images`,
40
+ client.projectId,
41
+ client.dataset,
42
+ `${image.id}-${image.width}x${image.height}.${image.format}`,
43
+ image.vanityName,
44
+ ]
45
+ .filter(Boolean)
46
+ .join("/")
47
+ );
48
+ if (params) {
49
+ url.search = new URLSearchParams(
50
+ sanityImageParamsToSearchParamEntries(params)
51
+ ).toString();
52
+ }
53
+ return url.href;
54
+ }
55
+
56
+ export function sanityImageSrcset(
57
+ client: SanityClientLike,
58
+ image: SanityImageAssetStub,
59
+ params?: Omit<SanityImageParams, "width">,
60
+ widths: number[] = defaultSrcsetWidths
61
+ ): string {
62
+ const aspectRatio = image.height / image.width;
63
+ return [
64
+ ...widths.sort((a, b) => a - b).filter((width) => width < image.width),
65
+ image.width,
66
+ ]
67
+ .map((width) => {
68
+ const url = sanityImageUrl(client, image, {
69
+ ...params,
70
+ width,
71
+ height: width * aspectRatio,
72
+ });
73
+ return `${url} ${width}w`;
74
+ })
75
+ .join(",");
76
+ }