@nonphoto/sanity-image 3.1.0 → 4.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,50 +1,44 @@
1
- interface SanityImageAssetLike {
1
+ interface AssetLike {
2
2
  _id: string;
3
3
  }
4
- declare function isSanityImageAssetLike(x: any): x is SanityImageAssetLike;
5
-
6
- interface SanityReference {
4
+ interface Reference {
7
5
  _ref: string;
8
6
  }
9
- declare function isSanityReference(x: any): x is SanityReference;
10
-
11
- interface SanityImageObject {
12
- asset: SanityImageAssetLike | SanityReference;
7
+ interface ImageAsset {
8
+ _id: string;
9
+ assetId: string;
10
+ width: number;
11
+ height: number;
12
+ extension: string;
13
13
  }
14
- declare function isSanityImageObject(x: any): x is SanityImageObject;
14
+ type ImageAssetSource = {
15
+ asset: ImageAssetSource;
16
+ } | ImageAsset | AssetLike | Reference | string;
17
+ declare function isAssetLike(input: unknown): input is AssetLike;
18
+ declare function isReference(input: unknown): input is Reference;
19
+ declare function isImageAsset(input: unknown): input is ImageAsset;
20
+ declare function hasAssetProp(input: unknown): input is {
21
+ asset: NonNullable<unknown>;
22
+ };
23
+ declare function imageIdFromSource(source: ImageAssetSource): string;
24
+ declare function imageIdFromUnknown(input: unknown): string | undefined;
25
+ declare function imageAsset(id: string): ImageAsset | undefined;
26
+ declare function imageAssetFromUnknown(input: unknown): ImageAsset | undefined;
15
27
 
16
- interface SanityImagePaletteSwatch {
17
- _type?: "sanity.imagePaletteSwatch" | null;
18
- background?: string | null;
19
- foreground?: string | null;
20
- population?: number | null;
21
- title?: string | null;
22
- }
23
- interface SanityImagePalette {
24
- _type?: "sanity.imagePalette" | null;
25
- darkMuted?: SanityImagePaletteSwatch | null;
26
- darkVibrant?: SanityImagePaletteSwatch | null;
27
- dominant?: SanityImagePaletteSwatch | null;
28
- lightMuted?: SanityImagePaletteSwatch | null;
29
- lightVibrant?: SanityImagePaletteSwatch | null;
30
- muted?: SanityImagePaletteSwatch | null;
31
- vibrant?: SanityImagePaletteSwatch | null;
32
- }
33
- interface SanityImageDimensions {
34
- _type?: "sanity.imageDimensions" | null;
35
- aspectRatio?: number | null;
36
- width?: number | null;
37
- height?: number | null;
38
- }
39
- interface SanityImageMetadata {
40
- _type: "sanity.imageMetadata";
41
- blurHash?: string | null;
42
- dimensions?: SanityImageDimensions | null;
43
- hasAlpha?: boolean | null;
44
- isOpaque?: boolean | null;
45
- lqip?: string | null;
46
- palette?: SanityImagePalette | null;
28
+ declare const defaultSrcsetWidths: number[];
29
+
30
+ type Crop = {
31
+ top?: number;
32
+ bottom?: number;
33
+ left?: number;
34
+ right?: number;
35
+ };
36
+
37
+ interface RectLike {
38
+ pos: ArrayLike<number>;
39
+ size: ArrayLike<number>;
47
40
  }
41
+ declare function rectFromCrop(asset: Pick<ImageAsset, "width" | "height">, crop: Crop): RectLike;
48
42
 
49
43
  type ImageFormat = "jpg" | "pjpg" | "png" | "webp";
50
44
  type FitMode = "clip" | "crop" | "fill" | "fillmax" | "max" | "scale" | "min";
@@ -52,7 +46,7 @@ type CropMode = "top" | "bottom" | "left" | "right" | "center" | "focalpoint" |
52
46
  type AutoMode = "format";
53
47
  type Orientation = 0 | 90 | 180 | 270;
54
48
  type Dpr = 1 | 2 | 3;
55
- type SanityImageParams = {
49
+ type ImageOptions = {
56
50
  auto?: AutoMode;
57
51
  background?: string;
58
52
  blur?: number;
@@ -62,10 +56,7 @@ type SanityImageParams = {
62
56
  fit?: FitMode;
63
57
  flipHorizontal?: boolean;
64
58
  flipVertical?: boolean;
65
- focalPoint?: {
66
- x: number;
67
- y: number;
68
- };
59
+ focalPoint?: ArrayLike<number>;
69
60
  format?: ImageFormat;
70
61
  frame?: number;
71
62
  height?: number;
@@ -77,36 +68,21 @@ type SanityImageParams = {
77
68
  orientation?: Orientation;
78
69
  pad?: number;
79
70
  quality?: number;
80
- rect?: {
81
- left: number;
82
- top: number;
83
- width: number;
84
- height: number;
85
- };
71
+ rect?: RectLike;
86
72
  saturation?: number;
87
73
  sharpen?: number;
88
74
  width?: number;
89
75
  };
90
- 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[][];
91
-
92
- type SanityImageSource = SanityImageObject | SanityReference | SanityImageAssetLike | string;
93
- interface SanityImageAssetStub {
94
- id: string;
95
- width: number;
96
- height: number;
97
- format: string;
98
- vanityName?: string;
99
- }
100
- declare function parseSanityImageAssetId(assetId: string): SanityImageAssetStub | undefined;
101
- declare function sanityImageAssetId(source: SanityImageSource): string;
102
- declare function sanityImageAssetStub(source: SanityImageSource): SanityImageAssetStub | undefined;
76
+ declare function imageOptionsToSearchParamEntries({ 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, }: ImageOptions): string[][];
103
77
 
104
- declare const defaultSrcsetWidths: number[];
105
78
  interface SanityClientLike {
106
79
  projectId: string;
107
80
  dataset: string;
108
81
  }
109
- declare function sanityImageUrl(client: SanityClientLike, image: SanityImageAssetStub, params?: SanityImageParams): string;
110
- declare function sanityImageSrcset(client: SanityClientLike, image: SanityImageAssetStub, params?: Omit<SanityImageParams, "width">, widths?: number[]): string;
82
+ interface ImageOptionsWithVanityName extends ImageOptions {
83
+ vanityName?: string;
84
+ }
85
+ declare function imageUrl(client: SanityClientLike, asset: ImageAsset, options?: ImageOptionsWithVanityName): string | undefined;
86
+ declare function imageSrcset(client: SanityClientLike, asset: ImageAsset, params?: (width: number) => Omit<ImageOptionsWithVanityName, "width">, widths?: number[]): string | undefined;
111
87
 
112
- export { type AutoMode, type CropMode, type Dpr, type FitMode, type ImageFormat, type Orientation, type SanityClientLike, type SanityImageAssetLike, type SanityImageAssetStub, type SanityImageDimensions, type SanityImageMetadata, type SanityImageObject, type SanityImagePalette, type SanityImagePaletteSwatch, type SanityImageParams, type SanityImageSource, type SanityReference, defaultSrcsetWidths, isSanityImageAssetLike, isSanityImageObject, isSanityReference, parseSanityImageAssetId, sanityImageAssetId, sanityImageAssetStub, sanityImageParamsToSearchParamEntries, sanityImageSrcset, sanityImageUrl };
88
+ export { type AssetLike, type AutoMode, type Crop, type CropMode, type Dpr, type FitMode, type ImageAsset, type ImageAssetSource, type ImageFormat, type ImageOptions, type ImageOptionsWithVanityName, type Orientation, type RectLike, type Reference, type SanityClientLike, defaultSrcsetWidths, hasAssetProp, imageAsset, imageAssetFromUnknown, imageIdFromSource, imageIdFromUnknown, imageOptionsToSearchParamEntries, imageSrcset, imageUrl, isAssetLike, isImageAsset, isReference, rectFromCrop };
package/dist/index.js CHANGED
@@ -1,20 +1,83 @@
1
1
  // src/asset.ts
2
- function isSanityImageAssetLike(x) {
3
- return x != null && typeof x === "object" && "_id" in x && typeof x._id === "string";
2
+ function isAssetLike(input) {
3
+ return input != null && typeof input === "object" && "_id" in input && typeof input._id === "string";
4
4
  }
5
-
6
- // src/reference.ts
7
- function isSanityReference(x) {
8
- return x != null && typeof x === "object" && "_ref" in x && typeof x._ref === "string";
5
+ function isReference(input) {
6
+ return input != null && typeof input === "object" && "_ref" in input && typeof input._ref === "string";
9
7
  }
10
-
11
- // src/imageObject.ts
12
- function isSanityImageObject(x) {
13
- return x != null && typeof x === "object" && "asset" in x && (isSanityImageAssetLike(x.asset) || isSanityReference(x.asset));
8
+ function isImageAsset(input) {
9
+ return input != null && typeof input === "object" && "assetId" in input && typeof input.assetId === "string" && "width" in input && typeof input.width === "number" && "height" in input && typeof input.height === "number" && "extension" in input && typeof input.extension === "string";
10
+ }
11
+ function hasAssetProp(input) {
12
+ return input != null && typeof input === "object" && "asset" in input;
14
13
  }
14
+ function imageIdFromSource(source) {
15
+ return typeof source === "string" ? source : isReference(source) ? source._ref : isAssetLike(source) ? source._id : imageIdFromSource(source.asset);
16
+ }
17
+ function imageIdFromUnknown(input) {
18
+ return typeof input === "string" ? input : isReference(input) ? input._ref : isAssetLike(input) ? input._id : hasAssetProp(input) ? imageIdFromUnknown(input.asset) : void 0;
19
+ }
20
+ function imageAsset(id) {
21
+ const matches = id.match(/^image-(\w+)-(\d+)x(\d+)-(\w+)$/);
22
+ if (matches) {
23
+ const [, assetId, width, height, extension] = matches;
24
+ return {
25
+ _id: id,
26
+ assetId,
27
+ width: Number(width),
28
+ height: Number(height),
29
+ extension
30
+ };
31
+ }
32
+ }
33
+ function imageAssetFromUnknown(input) {
34
+ if (isImageAsset(input)) {
35
+ return input;
36
+ } else {
37
+ const id = imageIdFromUnknown(input);
38
+ return id ? imageAsset(id) : void 0;
39
+ }
40
+ }
41
+
42
+ // src/constants.ts
43
+ var defaultSrcsetWidths = [
44
+ 6016,
45
+ // 6K
46
+ 5120,
47
+ // 5K
48
+ 4480,
49
+ // 4.5K
50
+ 3840,
51
+ // 4K
52
+ 3200,
53
+ // QHD+
54
+ 2560,
55
+ // WQXGA
56
+ 2048,
57
+ // QXGA
58
+ 1920,
59
+ // 1080p
60
+ 1668,
61
+ // iPad
62
+ 1280,
63
+ // 720p
64
+ 1080,
65
+ // iPhone 6-8 Plus
66
+ 960,
67
+ 720,
68
+ // iPhone 6-8
69
+ 640,
70
+ // 480p
71
+ 480,
72
+ 360,
73
+ 240
74
+ ];
15
75
 
16
- // src/params.ts
17
- function sanityImageParamsToSearchParamEntries({
76
+ // src/options.ts
77
+ function isValidEntry(entry) {
78
+ return typeof entry[0] === "string" && ["string", "number", "boolean"].includes(typeof entry[1]);
79
+ }
80
+ function imageOptionsToSearchParamEntries({
18
81
  auto,
19
82
  background,
20
83
  blur,
@@ -51,8 +114,8 @@ function sanityImageParamsToSearchParamEntries({
51
114
  fit,
52
115
  flip: [flipHorizontal && "h", flipVertical && "v"].filter(Boolean).join(""),
53
116
  fm: format,
54
- "fp-x": focalPoint?.x,
55
- "fp-y": focalPoint?.y,
117
+ "fp-x": focalPoint?.[0],
118
+ "fp-y": focalPoint?.[1],
56
119
  frame,
57
120
  h: height,
58
121
  invert,
@@ -63,107 +126,70 @@ function sanityImageParamsToSearchParamEntries({
63
126
  or: orientation,
64
127
  pad,
65
128
  q: quality,
66
- rect: rect ? [rect.left, rect.top, rect.width, rect.height].map(Math.round).join(",") : void 0,
129
+ rect: rect ? [rect.pos[0], rect.pos[1], rect.size[0], rect.size[1]].map(Math.round).join(",") : void 0,
67
130
  sat: saturation,
68
131
  sharp: sharpen,
69
132
  w: width
70
- }).filter(([, value]) => typeof value === "number" || Boolean(value)).map(([key, value]) => [
133
+ }).filter(isValidEntry).filter(([, value]) => typeof value === "number" || Boolean(value)).map(([key, value]) => [
71
134
  key,
72
- encodeURIComponent(
73
- typeof value === "number" ? Math.round(value) : value
74
- )
135
+ encodeURIComponent(typeof value === "number" ? Math.round(value) : value)
75
136
  ]);
76
137
  }
77
138
 
78
- // src/stub.ts
79
- function parseSanityImageAssetId(assetId) {
80
- const matches = assetId.match(/^image-(\w+)-(\d+)x(\d+)-(\w+)$/);
81
- if (matches) {
82
- const [, id, width, height, format] = matches;
83
- return { id, width: Number(width), height: Number(height), format };
84
- }
85
- }
86
- function sanityImageAssetId(source) {
87
- return typeof source === "string" ? source : isSanityReference(source) ? source._ref : isSanityImageAssetLike(source) ? source._id : sanityImageAssetId(source.asset);
88
- }
89
- function sanityImageAssetStub(source) {
90
- const id = sanityImageAssetId(source);
91
- return id ? parseSanityImageAssetId(id) : void 0;
92
- }
93
-
94
- // src/url.ts
95
- var defaultSrcsetWidths = [
96
- 6016,
97
- // 6K
98
- 5120,
99
- // 5K
100
- 4480,
101
- // 4.5K
102
- 3840,
103
- // 4K
104
- 3200,
105
- // QHD+
106
- 2560,
107
- // WQXGA
108
- 2048,
109
- // QXGA
110
- 1920,
111
- // 1080p
112
- 1668,
113
- // iPad
114
- 1280,
115
- // 720p
116
- 1080,
117
- // iPhone 6-8 Plus
118
- 960,
119
- 720,
120
- // iPhone 6-8
121
- 640,
122
- // 480p
123
- 480,
124
- 360,
125
- 240
126
- ];
127
- function sanityImageUrl(client, image, params) {
139
+ // src/image.ts
140
+ function imageUrl(client, asset, options) {
128
141
  const url = new URL(
129
142
  [
130
143
  `https://cdn.sanity.io/images`,
131
144
  client.projectId,
132
145
  client.dataset,
133
- `${image.id}-${image.width}x${image.height}.${image.format}`,
134
- image.vanityName
146
+ `${asset.assetId}-${asset.width}x${asset.height}.${asset.extension}`,
147
+ options?.vanityName
135
148
  ].filter(Boolean).join("/")
136
149
  );
137
- if (params) {
150
+ if (options) {
138
151
  url.search = new URLSearchParams(
139
- sanityImageParamsToSearchParamEntries(params)
152
+ imageOptionsToSearchParamEntries(options)
140
153
  ).toString();
141
154
  }
142
155
  return url.href;
143
156
  }
144
- function sanityImageSrcset(client, image, params, widths = defaultSrcsetWidths) {
145
- const aspectRatio = image.height / image.width;
157
+ function imageSrcset(client, asset, params, widths = defaultSrcsetWidths) {
146
158
  return [
147
- ...widths.sort((a, b) => a - b).filter((width) => width < image.width),
148
- image.width
159
+ ...widths.sort((a, b) => a - b).filter((width) => width < asset.width),
160
+ asset.width
149
161
  ].map((width) => {
150
- const url = sanityImageUrl(client, image, {
151
- ...params,
152
- width,
153
- height: width * aspectRatio
162
+ const url = imageUrl(client, asset, {
163
+ ...params?.(width),
164
+ width
154
165
  });
155
166
  return `${url} ${width}w`;
156
167
  }).join(",");
157
168
  }
169
+
170
+ // src/rect.ts
171
+ function rectFromCrop(asset, crop) {
172
+ const left = crop.left ?? 0;
173
+ const right = crop.right ?? 0;
174
+ const top = crop.top ?? 0;
175
+ const bottom = crop.bottom ?? 0;
176
+ return {
177
+ pos: [left * asset.width, right * asset.width],
178
+ size: [(1 - left - right) * asset.width, (1 - top - bottom) * asset.height]
179
+ };
180
+ }
158
181
  export {
159
182
  defaultSrcsetWidths,
160
- isSanityImageAssetLike,
161
- isSanityImageObject,
162
- isSanityReference,
163
- parseSanityImageAssetId,
164
- sanityImageAssetId,
165
- sanityImageAssetStub,
166
- sanityImageParamsToSearchParamEntries,
167
- sanityImageSrcset,
168
- sanityImageUrl
183
+ hasAssetProp,
184
+ imageAsset,
185
+ imageAssetFromUnknown,
186
+ imageIdFromSource,
187
+ imageIdFromUnknown,
188
+ imageOptionsToSearchParamEntries,
189
+ imageSrcset,
190
+ imageUrl,
191
+ isAssetLike,
192
+ isImageAsset,
193
+ isReference,
194
+ rectFromCrop
169
195
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonphoto/sanity-image",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "author": "Jonas Luebbers <jonas@jonasluebbers.com> (https://www.jonasluebbers.com)",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/asset.ts CHANGED
@@ -1,12 +1,108 @@
1
- export interface SanityImageAssetLike {
1
+ export interface AssetLike {
2
2
  _id: string;
3
3
  }
4
4
 
5
- export function isSanityImageAssetLike(x: any): x is SanityImageAssetLike {
5
+ export interface Reference {
6
+ _ref: string;
7
+ }
8
+
9
+ export interface ImageAsset {
10
+ _id: string;
11
+ assetId: string;
12
+ width: number;
13
+ height: number;
14
+ extension: string;
15
+ }
16
+
17
+ export type ImageAssetSource =
18
+ | {
19
+ asset: ImageAssetSource;
20
+ }
21
+ | ImageAsset
22
+ | AssetLike
23
+ | Reference
24
+ | string;
25
+
26
+ export function isAssetLike(input: unknown): input is AssetLike {
27
+ return (
28
+ input != null &&
29
+ typeof input === "object" &&
30
+ "_id" in input &&
31
+ typeof input._id === "string"
32
+ );
33
+ }
34
+
35
+ export function isReference(input: unknown): input is Reference {
36
+ return (
37
+ input != null &&
38
+ typeof input === "object" &&
39
+ "_ref" in input &&
40
+ typeof input._ref === "string"
41
+ );
42
+ }
43
+
44
+ export function isImageAsset(input: unknown): input is ImageAsset {
6
45
  return (
7
- x != null &&
8
- typeof x === "object" &&
9
- "_id" in x &&
10
- typeof x._id === "string"
46
+ input != null &&
47
+ typeof input === "object" &&
48
+ "assetId" in input &&
49
+ typeof input.assetId === "string" &&
50
+ "width" in input &&
51
+ typeof input.width === "number" &&
52
+ "height" in input &&
53
+ typeof input.height === "number" &&
54
+ "extension" in input &&
55
+ typeof input.extension === "string"
11
56
  );
12
57
  }
58
+
59
+ export function hasAssetProp(
60
+ input: unknown,
61
+ ): input is { asset: NonNullable<unknown> } {
62
+ return input != null && typeof input === "object" && "asset" in input;
63
+ }
64
+
65
+ export function imageIdFromSource(source: ImageAssetSource): string {
66
+ return typeof source === "string"
67
+ ? source
68
+ : isReference(source)
69
+ ? source._ref
70
+ : isAssetLike(source)
71
+ ? source._id
72
+ : imageIdFromSource(source.asset);
73
+ }
74
+
75
+ export function imageIdFromUnknown(input: unknown): string | undefined {
76
+ return typeof input === "string"
77
+ ? input
78
+ : isReference(input)
79
+ ? input._ref
80
+ : isAssetLike(input)
81
+ ? input._id
82
+ : hasAssetProp(input)
83
+ ? imageIdFromUnknown(input.asset)
84
+ : undefined;
85
+ }
86
+
87
+ export function imageAsset(id: string): ImageAsset | undefined {
88
+ const matches = id.match(/^image-(\w+)-(\d+)x(\d+)-(\w+)$/);
89
+ if (matches) {
90
+ const [, assetId, width, height, extension] = matches;
91
+ return {
92
+ _id: id,
93
+ assetId,
94
+ width: Number(width),
95
+ height: Number(height),
96
+ extension,
97
+ };
98
+ }
99
+ }
100
+
101
+ export function imageAssetFromUnknown(input: unknown): ImageAsset | undefined {
102
+ if (isImageAsset(input)) {
103
+ return input;
104
+ } else {
105
+ const id = imageIdFromUnknown(input);
106
+ return id ? imageAsset(id) : undefined;
107
+ }
108
+ }
@@ -0,0 +1,19 @@
1
+ export const defaultSrcsetWidths = [
2
+ 6016, // 6K
3
+ 5120, // 5K
4
+ 4480, // 4.5K
5
+ 3840, // 4K
6
+ 3200, // QHD+
7
+ 2560, // WQXGA
8
+ 2048, // QXGA
9
+ 1920, // 1080p
10
+ 1668, // iPad
11
+ 1280, // 720p
12
+ 1080, // iPhone 6-8 Plus
13
+ 960,
14
+ 720, // iPhone 6-8
15
+ 640, // 480p
16
+ 480,
17
+ 360,
18
+ 240,
19
+ ];
package/src/crop.ts ADDED
@@ -0,0 +1,6 @@
1
+ export type Crop = {
2
+ top?: number;
3
+ bottom?: number;
4
+ left?: number;
5
+ right?: number;
6
+ };
package/src/image.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { ImageAsset } from "./asset";
2
+ import { defaultSrcsetWidths } from "./constants";
3
+ import { ImageOptions, imageOptionsToSearchParamEntries } from "./options";
4
+
5
+ export interface SanityClientLike {
6
+ projectId: string;
7
+ dataset: string;
8
+ }
9
+
10
+ export interface ImageOptionsWithVanityName extends ImageOptions {
11
+ vanityName?: string;
12
+ }
13
+
14
+ export function imageUrl(
15
+ client: SanityClientLike,
16
+ asset: ImageAsset,
17
+ options?: ImageOptionsWithVanityName,
18
+ ): string | undefined {
19
+ const url = new URL(
20
+ [
21
+ `https://cdn.sanity.io/images`,
22
+ client.projectId,
23
+ client.dataset,
24
+ `${asset.assetId}-${asset.width}x${asset.height}.${asset.extension}`,
25
+ options?.vanityName,
26
+ ]
27
+ .filter(Boolean)
28
+ .join("/"),
29
+ );
30
+ if (options) {
31
+ url.search = new URLSearchParams(
32
+ imageOptionsToSearchParamEntries(options),
33
+ ).toString();
34
+ }
35
+ return url.href;
36
+ }
37
+
38
+ export function imageSrcset(
39
+ client: SanityClientLike,
40
+ asset: ImageAsset,
41
+ params?: (width: number) => Omit<ImageOptionsWithVanityName, "width">,
42
+ widths: number[] = defaultSrcsetWidths,
43
+ ): string | undefined {
44
+ return [
45
+ ...widths.sort((a, b) => a - b).filter((width) => width < asset.width),
46
+ asset.width,
47
+ ]
48
+ .map((width) => {
49
+ const url = imageUrl(client, asset, {
50
+ ...params?.(width),
51
+ width,
52
+ });
53
+ return `${url} ${width}w`;
54
+ })
55
+ .join(",");
56
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from "./asset";
2
- export * from "./imageObject";
3
- export * from "./metadata";
4
- export * from "./params";
5
- export * from "./reference";
6
- export * from "./stub";
7
- export * from "./url";
2
+ export * from "./constants";
3
+ export * from "./crop";
4
+ export * from "./image";
5
+ export * from "./options";
6
+ export * from "./rect";
@@ -1,3 +1,5 @@
1
+ import { RectLike } from "./rect";
2
+
1
3
  export type ImageFormat = "jpg" | "pjpg" | "png" | "webp";
2
4
 
3
5
  export type FitMode =
@@ -24,7 +26,7 @@ export type Orientation = 0 | 90 | 180 | 270;
24
26
 
25
27
  export type Dpr = 1 | 2 | 3;
26
28
 
27
- export type SanityImageParams = {
29
+ export type ImageOptions = {
28
30
  auto?: AutoMode;
29
31
  background?: string;
30
32
  blur?: number;
@@ -34,7 +36,7 @@ export type SanityImageParams = {
34
36
  fit?: FitMode;
35
37
  flipHorizontal?: boolean;
36
38
  flipVertical?: boolean;
37
- focalPoint?: { x: number; y: number };
39
+ focalPoint?: ArrayLike<number>;
38
40
  format?: ImageFormat;
39
41
  frame?: number;
40
42
  height?: number;
@@ -46,13 +48,22 @@ export type SanityImageParams = {
46
48
  orientation?: Orientation;
47
49
  pad?: number;
48
50
  quality?: number;
49
- rect?: { left: number; top: number; width: number; height: number };
51
+ rect?: RectLike;
50
52
  saturation?: number;
51
53
  sharpen?: number;
52
54
  width?: number;
53
55
  };
54
56
 
55
- export function sanityImageParamsToSearchParamEntries({
57
+ type ValidEntry = [string, string | number | boolean];
58
+
59
+ function isValidEntry(entry: [unknown, unknown]): entry is ValidEntry {
60
+ return (
61
+ typeof entry[0] === "string" &&
62
+ ["string", "number", "boolean"].includes(typeof entry[1])
63
+ );
64
+ }
65
+
66
+ export function imageOptionsToSearchParamEntries({
56
67
  auto,
57
68
  background,
58
69
  blur,
@@ -78,7 +89,7 @@ export function sanityImageParamsToSearchParamEntries({
78
89
  saturation,
79
90
  sharpen,
80
91
  width,
81
- }: SanityImageParams): string[][] {
92
+ }: ImageOptions): string[][] {
82
93
  return Object.entries({
83
94
  auto,
84
95
  bg: background,
@@ -89,8 +100,8 @@ export function sanityImageParamsToSearchParamEntries({
89
100
  fit,
90
101
  flip: [flipHorizontal && "h", flipVertical && "v"].filter(Boolean).join(""),
91
102
  fm: format,
92
- "fp-x": focalPoint?.x,
93
- "fp-y": focalPoint?.y,
103
+ "fp-x": focalPoint?.[0],
104
+ "fp-y": focalPoint?.[1],
94
105
  frame,
95
106
  h: height,
96
107
  invert,
@@ -102,17 +113,18 @@ export function sanityImageParamsToSearchParamEntries({
102
113
  pad,
103
114
  q: quality,
104
115
  rect: rect
105
- ? [rect.left, rect.top, rect.width, rect.height].map(Math.round).join(",")
116
+ ? [rect.pos[0], rect.pos[1], rect.size[0], rect.size[1]]
117
+ .map(Math.round)
118
+ .join(",")
106
119
  : undefined,
107
120
  sat: saturation,
108
121
  sharp: sharpen,
109
122
  w: width,
110
123
  })
124
+ .filter(isValidEntry)
111
125
  .filter(([, value]) => typeof value === "number" || Boolean(value))
112
126
  .map(([key, value]) => [
113
127
  key,
114
- encodeURIComponent(
115
- typeof value === "number" ? Math.round(value) : (value as string)
116
- ),
128
+ encodeURIComponent(typeof value === "number" ? Math.round(value) : value),
117
129
  ]);
118
130
  }
package/src/rect.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { ImageAsset } from "./asset";
2
+ import { Crop } from "./crop";
3
+
4
+ export interface RectLike {
5
+ pos: ArrayLike<number>;
6
+ size: ArrayLike<number>;
7
+ }
8
+
9
+ export function rectFromCrop(
10
+ asset: Pick<ImageAsset, "width" | "height">,
11
+ crop: Crop,
12
+ ): RectLike {
13
+ const left = crop.left ?? 0;
14
+ const right = crop.right ?? 0;
15
+ const top = crop.top ?? 0;
16
+ const bottom = crop.bottom ?? 0;
17
+ return {
18
+ pos: [left * asset.width, right * asset.width],
19
+ size: [(1 - left - right) * asset.width, (1 - top - bottom) * asset.height],
20
+ };
21
+ }
@@ -1,15 +0,0 @@
1
- import { isSanityImageAssetLike, SanityImageAssetLike } from "./asset";
2
- import { isSanityReference, SanityReference } from "./reference";
3
-
4
- export interface SanityImageObject {
5
- asset: SanityImageAssetLike | SanityReference;
6
- }
7
-
8
- export function isSanityImageObject(x: any): x is SanityImageObject {
9
- return (
10
- x != null &&
11
- typeof x === "object" &&
12
- "asset" in x &&
13
- (isSanityImageAssetLike(x.asset) || isSanityReference(x.asset))
14
- );
15
- }
package/src/metadata.ts DELETED
@@ -1,35 +0,0 @@
1
- export interface SanityImagePaletteSwatch {
2
- _type?: "sanity.imagePaletteSwatch" | null;
3
- background?: string | null;
4
- foreground?: string | null;
5
- population?: number | null;
6
- title?: string | null;
7
- }
8
-
9
- export interface SanityImagePalette {
10
- _type?: "sanity.imagePalette" | null;
11
- darkMuted?: SanityImagePaletteSwatch | null;
12
- darkVibrant?: SanityImagePaletteSwatch | null;
13
- dominant?: SanityImagePaletteSwatch | null;
14
- lightMuted?: SanityImagePaletteSwatch | null;
15
- lightVibrant?: SanityImagePaletteSwatch | null;
16
- muted?: SanityImagePaletteSwatch | null;
17
- vibrant?: SanityImagePaletteSwatch | null;
18
- }
19
-
20
- export interface SanityImageDimensions {
21
- _type?: "sanity.imageDimensions" | null;
22
- aspectRatio?: number | null;
23
- width?: number | null;
24
- height?: number | null;
25
- }
26
-
27
- export interface SanityImageMetadata {
28
- _type: "sanity.imageMetadata";
29
- blurHash?: string | null;
30
- dimensions?: SanityImageDimensions | null;
31
- hasAlpha?: boolean | null;
32
- isOpaque?: boolean | null;
33
- lqip?: string | null;
34
- palette?: SanityImagePalette | null;
35
- }
package/src/reference.ts DELETED
@@ -1,12 +0,0 @@
1
- export interface SanityReference {
2
- _ref: string;
3
- }
4
-
5
- export function isSanityReference(x: any): x is SanityReference {
6
- return (
7
- x != null &&
8
- typeof x === "object" &&
9
- "_ref" in x &&
10
- typeof x._ref === "string"
11
- );
12
- }
package/src/stub.ts DELETED
@@ -1,44 +0,0 @@
1
- import { isSanityImageAssetLike, SanityImageAssetLike } from "./asset";
2
- import { SanityImageObject } from "./imageObject";
3
- import { isSanityReference, SanityReference } from "./reference";
4
-
5
- export type SanityImageSource =
6
- | SanityImageObject
7
- | SanityReference
8
- | SanityImageAssetLike
9
- | string;
10
-
11
- export interface SanityImageAssetStub {
12
- id: string;
13
- width: number;
14
- height: number;
15
- format: string;
16
- vanityName?: string;
17
- }
18
-
19
- export function parseSanityImageAssetId(
20
- assetId: string
21
- ): SanityImageAssetStub | undefined {
22
- const matches = assetId.match(/^image-(\w+)-(\d+)x(\d+)-(\w+)$/);
23
- if (matches) {
24
- const [, id, width, height, format] = matches;
25
- return { id, width: Number(width), height: Number(height), format };
26
- }
27
- }
28
-
29
- export function sanityImageAssetId(source: SanityImageSource): string {
30
- return typeof source === "string"
31
- ? source
32
- : isSanityReference(source)
33
- ? source._ref
34
- : isSanityImageAssetLike(source)
35
- ? source._id
36
- : sanityImageAssetId(source.asset);
37
- }
38
-
39
- export function sanityImageAssetStub(
40
- source: SanityImageSource
41
- ): SanityImageAssetStub | undefined {
42
- const id = sanityImageAssetId(source);
43
- return id ? parseSanityImageAssetId(id) : undefined;
44
- }
package/src/url.ts DELETED
@@ -1,76 +0,0 @@
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
- }