@nonphoto/sanity-image 1.0.1 → 2.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,31 +1,58 @@
1
- import { SanityImageDimensions, SanityClientLike, SanityProjectDetails, SanityModernClientLike, SanityImageSource } from '@sanity/image-url/lib/types/types.js';
1
+ import { SanityImageObject, SanityImageDimensions, SanityImageCrop, SanityImageHotspot, SanityReference, SanityAsset, SanityClientLike, SanityProjectDetails, SanityModernClientLike, SanityImageSource } from '@sanity/image-url/lib/types/types.js';
2
2
 
3
- interface Metadata {
3
+ interface SanityImageObjectLike extends Pick<SanityImageObject, "asset"> {
4
+ [key: string]: any;
5
+ }
6
+ interface SanityImageMetadata {
4
7
  lqip?: string;
5
8
  dimensions?: SanityImageDimensions;
9
+ [key: string]: any;
6
10
  }
7
11
  interface Size {
8
12
  width: number;
9
13
  height: number;
10
14
  }
11
15
  declare const defaultWidths: number[];
12
- declare const lowResWidth = 24;
13
- declare const defaultMetaImageWidth = 1200;
16
+ declare const defaultWidth = 1280;
14
17
  declare const defaultQuality = 90;
15
- interface ImagePropsOptions {
18
+ declare const fitComparators: {
19
+ cover: (...values: number[]) => number;
20
+ contain: (...values: number[]) => number;
21
+ };
22
+ declare function fit(containee: Size, container: Size, mode: keyof typeof fitComparators): {
23
+ width: number;
24
+ 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 {
16
36
  client: SanityClientLike | SanityProjectDetails | SanityModernClientLike;
17
- image: SanityImageSource;
18
- metadata?: Metadata;
19
- widths?: number[];
37
+ source: SanityImageSource;
38
+ width: number;
20
39
  quality?: number;
21
40
  aspectRatio?: number;
22
41
  }
23
- interface ImagePropsReturn {
24
- src: string;
25
- srcset?: string;
26
- naturalWidth?: number;
27
- naturalHeight?: number;
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;
28
49
  }
29
- declare function imageProps({ client, image, metadata, widths, quality, aspectRatio, }: ImagePropsOptions): ImagePropsReturn;
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;
30
57
 
31
- export { type ImagePropsOptions, type ImagePropsReturn, type Metadata, type Size, defaultMetaImageWidth, defaultQuality, defaultWidths, imageProps, lowResWidth };
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 };
package/dist/index.js CHANGED
@@ -32,13 +32,11 @@ var defaultWidths = [
32
32
  360,
33
33
  240
34
34
  ];
35
- var lowResWidth = 24;
36
- var defaultMetaImageWidth = 1200;
35
+ var defaultWidth = 1280;
37
36
  var defaultQuality = 90;
38
37
  var fitComparators = {
39
38
  cover: Math.max,
40
- contain: Math.min,
41
- mean: (a, b) => (a + b) / 2
39
+ contain: Math.min
42
40
  };
43
41
  function fit(containee, container, mode) {
44
42
  const sx = container.width / containee.width;
@@ -49,45 +47,101 @@ function fit(containee, container, mode) {
49
47
  height: containee.height * s
50
48
  };
51
49
  }
52
- function buildAspectRatio(builder, width, aspectRatio) {
53
- if (aspectRatio) {
54
- return builder.width(width).height(Math.round(width * aspectRatio));
55
- } else {
56
- return builder.width(width);
57
- }
58
- }
59
50
  function isCrop(x) {
60
- return x && 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";
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) || isReference(x));
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";
61
64
  }
62
- function imageProps({
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({
63
78
  client,
64
- image,
65
- metadata,
66
- widths,
79
+ source,
80
+ width,
67
81
  quality = defaultQuality,
68
- aspectRatio
82
+ aspectRatio: aspectRatio2
69
83
  }) {
70
- const builder = imageUrlBuilder(client).image(image).quality(quality).auto("format");
71
- const crop = typeof image == "object" && "crop" in image && isCrop(image.crop) ? image.crop : void 0;
72
- const cropSize = metadata?.dimensions ? crop ? {
73
- width: metadata.dimensions.width - crop.left - crop.right,
74
- height: metadata.dimensions.height - crop.top - crop.bottom
75
- } : metadata.dimensions : void 0;
76
- const naturalSize = cropSize ? aspectRatio ? fit({ width: 1, height: aspectRatio }, cropSize, "contain") : cropSize : void 0;
77
- const url = buildAspectRatio(builder, lowResWidth, aspectRatio).url();
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();
97
+ return `${url} ${width}w`;
98
+ }).join(",");
99
+ }
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) {
78
104
  return {
79
- src: widths ? url : metadata?.lqip ?? url,
80
- srcset: widths ? Array.from(widths).sort((a, b) => a - b).map(
81
- (width) => `${buildAspectRatio(builder, width, aspectRatio).url()} ${width}w`
82
- ).join(",") : void 0,
83
- naturalWidth: naturalSize?.width,
84
- naturalHeight: naturalSize?.height
105
+ width: intrinsicSize.width - crop.left - crop.right,
106
+ height: intrinsicSize.height - crop.top - crop.bottom
85
107
  };
86
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
+ }
87
124
  export {
88
- defaultMetaImageWidth,
125
+ aspectRatio,
126
+ croppedSize,
89
127
  defaultQuality,
128
+ defaultWidth,
90
129
  defaultWidths,
91
- imageProps,
92
- lowResWidth
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
93
147
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonphoto/sanity-image",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "publishConfig": {
@@ -34,15 +34,15 @@
34
34
  "author": "Jonas Luebbers <jonas@jonasluebbers.com> (https://www.jonasluebbers.com)",
35
35
  "license": "MIT",
36
36
  "devDependencies": {
37
- "semantic-release": "24.2.3",
38
- "tsup": "8.4.0",
39
- "typescript": "5.8.2"
37
+ "semantic-release": "24.2.7",
38
+ "tsup": "8.5.0",
39
+ "typescript": "5.9.2"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=20",
43
43
  "pnpm": ">=9"
44
44
  },
45
45
  "dependencies": {
46
- "@sanity/image-url": "1.0.2"
46
+ "@sanity/image-url": "^1.1.0"
47
47
  }
48
48
  }
package/src/index.ts CHANGED
@@ -1,17 +1,26 @@
1
1
  import imageUrlBuilder from "@sanity/image-url";
2
- import { ImageUrlBuilder } from "@sanity/image-url/lib/types/builder";
3
2
  import type {
3
+ SanityAsset,
4
4
  SanityClientLike,
5
5
  SanityImageCrop,
6
6
  SanityImageDimensions,
7
+ SanityImageHotspot,
8
+ SanityImageObject,
7
9
  SanityImageSource,
8
10
  SanityModernClientLike,
9
11
  SanityProjectDetails,
12
+ SanityReference,
10
13
  } from "@sanity/image-url/lib/types/types.js";
11
14
 
12
- export interface Metadata {
15
+ export interface SanityImageObjectLike
16
+ extends Pick<SanityImageObject, "asset"> {
17
+ [key: string]: any;
18
+ }
19
+
20
+ export interface SanityImageMetadata {
13
21
  lqip?: string;
14
22
  dimensions?: SanityImageDimensions;
23
+ [key: string]: any;
15
24
  }
16
25
 
17
26
  export interface Size {
@@ -39,19 +48,17 @@ export const defaultWidths = [
39
48
  240,
40
49
  ];
41
50
 
42
- export const lowResWidth = 24;
43
-
44
- export const defaultMetaImageWidth = 1200;
51
+ export const defaultWidth = 1280;
45
52
 
46
53
  export const defaultQuality = 90;
47
54
 
48
- const fitComparators = {
55
+ export const fitComparators = {
49
56
  cover: Math.max,
50
57
  contain: Math.min,
51
- mean: (a: number, b: number) => (a + b) / 2,
52
58
  };
53
59
 
54
- function fit(
60
+ // TODO: impement comparators for every FitMode
61
+ export function fit(
55
62
  containee: Size,
56
63
  container: Size,
57
64
  mode: keyof typeof fitComparators
@@ -65,21 +72,9 @@ function fit(
65
72
  };
66
73
  }
67
74
 
68
- function buildAspectRatio(
69
- builder: ImageUrlBuilder,
70
- width: number,
71
- aspectRatio?: number
72
- ) {
73
- if (aspectRatio) {
74
- return builder.width(width).height(Math.round(width * aspectRatio));
75
- } else {
76
- return builder.width(width);
77
- }
78
- }
79
-
80
- function isCrop(x: any): x is SanityImageCrop {
75
+ export function isCrop(x: unknown): x is SanityImageCrop {
81
76
  return (
82
- x &&
77
+ x != null &&
83
78
  typeof x == "object" &&
84
79
  "top" in x &&
85
80
  typeof x.top === "number" &&
@@ -88,69 +83,196 @@ function isCrop(x: any): x is SanityImageCrop {
88
83
  "bottom" in x &&
89
84
  typeof x.bottom === "number" &&
90
85
  "left" in x &&
91
- typeof x.left === "number"
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) || isReference(x))
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)
92
180
  );
93
181
  }
94
182
 
95
- export interface ImagePropsOptions {
183
+ export interface ImageSrcOptions {
96
184
  client: SanityClientLike | SanityProjectDetails | SanityModernClientLike;
97
- image: SanityImageSource;
98
- metadata?: Metadata;
99
- widths?: number[];
185
+ source: SanityImageSource;
186
+ width: number;
100
187
  quality?: number;
101
188
  aspectRatio?: number;
102
189
  }
103
190
 
104
- export interface ImagePropsReturn {
105
- src: string;
106
- srcset?: string;
107
- naturalWidth?: number;
108
- naturalHeight?: number;
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;
109
212
  }
110
213
 
111
- export function imageProps({
214
+ export function imageSrcset({
112
215
  client,
113
- image,
114
- metadata,
115
- widths,
216
+ source,
217
+ widths = defaultWidths,
116
218
  quality = defaultQuality,
117
219
  aspectRatio,
118
- }: ImagePropsOptions): ImagePropsReturn {
220
+ }: ImageSrcsetOptions): string {
119
221
  const builder = imageUrlBuilder(client)
120
- .image(image)
222
+ .image(source)
121
223
  .quality(quality)
122
224
  .auto("format");
123
- const crop =
124
- typeof image == "object" && "crop" in image && isCrop(image.crop)
125
- ? image.crop
126
- : undefined;
127
- const cropSize = metadata?.dimensions
128
- ? crop
129
- ? {
130
- width: metadata.dimensions.width - crop.left - crop.right,
131
- height: metadata.dimensions.height - crop.top - crop.bottom,
132
- }
133
- : metadata.dimensions
134
- : undefined;
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
+ }
135
235
 
136
- const naturalSize = cropSize
137
- ? aspectRatio
138
- ? fit({ width: 1, height: aspectRatio }, cropSize, "contain")
139
- : cropSize
236
+ export function imageAlt(source: SanityImageSource): string | undefined {
237
+ return isImageObjectLike(source) &&
238
+ "alt" in source &&
239
+ typeof source.alt === "string"
240
+ ? source.alt
140
241
  : undefined;
141
- const url = buildAspectRatio(builder, lowResWidth, aspectRatio).url();
242
+ }
243
+
244
+ export function croppedSize(intrinsicSize: Size, crop: SanityImageCrop): Size {
142
245
  return {
143
- src: widths ? url : metadata?.lqip ?? url,
144
- srcset: widths
145
- ? Array.from(widths)
146
- .sort((a, b) => a - b)
147
- .map(
148
- (width) =>
149
- `${buildAspectRatio(builder, width, aspectRatio).url()} ${width}w`
150
- )
151
- .join(",")
152
- : undefined,
153
- naturalWidth: naturalSize?.width,
154
- naturalHeight: naturalSize?.height,
246
+ width: intrinsicSize.width - crop.left - crop.right,
247
+ height: intrinsicSize.height - crop.top - crop.bottom,
155
248
  };
156
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
+ }