@nonphoto/sanity-image 3.2.0 → 5.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/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./asset";
2
- export * from "./imageObject";
3
- export * from "./params";
4
- export * from "./reference";
5
- export * from "./stub";
6
- export * from "./url";
2
+ export * from "./constants";
3
+ export * from "./crop";
4
+ export * from "./hotspot";
5
+ export * from "./image";
6
+ export * from "./rect";
7
+ export * from "./transformations";
package/src/rect.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { InferOutput, is, number, object, tuple } from "valibot";
2
+ import { ImageAsset } from "./asset";
3
+ import { Crop } from "./crop";
4
+
5
+ export const rectSchema = object({
6
+ pos: tuple([number(), number()]),
7
+ size: tuple([number(), number()]),
8
+ });
9
+
10
+ export type Rect = InferOutput<typeof rectSchema>;
11
+
12
+ export function isRect(input: unknown): input is Rect {
13
+ return is(rectSchema, input);
14
+ }
15
+
16
+ export function rectFromCrop(
17
+ asset: Pick<ImageAsset, "width" | "height">,
18
+ crop: Crop,
19
+ ): Rect {
20
+ const left = Math.max(crop.left ?? 0, 0);
21
+ const right = Math.max(crop.right ?? 0, 0);
22
+ const top = Math.max(crop.top ?? 0, 0);
23
+ const bottom = Math.max(crop.bottom ?? 0, 0);
24
+ return {
25
+ pos: [left * asset.width, top * asset.height],
26
+ size: [(1 - left - right) * asset.width, (1 - top - bottom) * asset.height],
27
+ };
28
+ }
@@ -0,0 +1,155 @@
1
+ import {
2
+ boolean,
3
+ InferOutput,
4
+ is,
5
+ literal,
6
+ number,
7
+ object,
8
+ partial,
9
+ string,
10
+ tuple,
11
+ union,
12
+ } from "valibot";
13
+ import { rectSchema } from "./rect";
14
+
15
+ export const transformationsSchema = partial(
16
+ object({
17
+ auto: literal("format"),
18
+ background: string(),
19
+ blur: number(),
20
+ crop: union([
21
+ literal("top"),
22
+ literal("bottom"),
23
+ literal("left"),
24
+ literal("right"),
25
+ literal("center"),
26
+ literal("focalpoint"),
27
+ literal("entropy"),
28
+ ]),
29
+ download: union([string(), boolean()]),
30
+ dpr: union([literal(1), literal(2), literal(3)]),
31
+ fit: union([
32
+ literal("clip"),
33
+ literal("crop"),
34
+ literal("fill"),
35
+ literal("fillmax"),
36
+ literal("max"),
37
+ literal("scale"),
38
+ literal("min"),
39
+ ]),
40
+ flipHorizontal: boolean(),
41
+ flipVertical: boolean(),
42
+ focalPoint: tuple([number(), number()]),
43
+ format: union([
44
+ literal("jpg"),
45
+ literal("pjpg"),
46
+ literal("png"),
47
+ literal("webp"),
48
+ ]),
49
+ frame: number(),
50
+ height: number(),
51
+ invert: boolean(),
52
+ maxHeight: number(),
53
+ maxWidth: number(),
54
+ minHeight: number(),
55
+ minWidth: number(),
56
+ orientation: union([literal(0), literal(90), literal(180), literal(270)]),
57
+ pad: number(),
58
+ quality: number(),
59
+ rect: rectSchema,
60
+ saturation: number(),
61
+ sharpen: number(),
62
+ width: number(),
63
+ }),
64
+ );
65
+
66
+ export type Transformations = InferOutput<typeof transformationsSchema>;
67
+
68
+ export function isTransformations(input: unknown): input is Transformations {
69
+ return is(transformationsSchema, input);
70
+ }
71
+
72
+ function entry(
73
+ key: string,
74
+ value: string | number | boolean | null | undefined,
75
+ ): [string, string] | undefined {
76
+ return value == null || value === false
77
+ ? undefined
78
+ : [key, String(typeof value === "number" ? Math.round(value) : value)];
79
+ }
80
+
81
+ export function transformationsToURLSearch({
82
+ auto,
83
+ background,
84
+ blur,
85
+ crop,
86
+ download,
87
+ dpr,
88
+ fit,
89
+ flipHorizontal,
90
+ flipVertical,
91
+ focalPoint,
92
+ format,
93
+ frame,
94
+ height,
95
+ invert,
96
+ maxHeight,
97
+ maxWidth,
98
+ minHeight,
99
+ minWidth,
100
+ orientation,
101
+ pad,
102
+ quality,
103
+ rect,
104
+ saturation,
105
+ sharpen,
106
+ width,
107
+ }: Transformations): string {
108
+ return (
109
+ "?" +
110
+ [
111
+ entry("auto", auto),
112
+ entry("bg", background),
113
+ entry("blur", blur),
114
+ entry("crop", crop),
115
+ entry("dl", download),
116
+ entry("dpr", dpr),
117
+ entry("fit", fit),
118
+ entry(
119
+ "flip",
120
+ flipHorizontal || flipVertical
121
+ ? [flipHorizontal && "h", flipVertical && "v"]
122
+ .filter(Boolean)
123
+ .join("")
124
+ : undefined,
125
+ ),
126
+ entry("fm", format),
127
+ entry("fp-x", focalPoint?.[0]),
128
+ entry("fp-y", focalPoint?.[1]),
129
+ entry("frame", frame),
130
+ entry("h", height),
131
+ entry("invert", invert),
132
+ entry("max-h", maxHeight),
133
+ entry("max-w", maxWidth),
134
+ entry("min-h", minHeight),
135
+ entry("min-w", minWidth),
136
+ entry("or", orientation),
137
+ entry("pad", pad),
138
+ entry("q", quality),
139
+ entry(
140
+ "rect",
141
+ rect
142
+ ? [rect.pos[0], rect.pos[1], rect.size[0], rect.size[1]]
143
+ .map(Math.round)
144
+ .join(",")
145
+ : undefined,
146
+ ),
147
+ entry("sat", saturation),
148
+ entry("sharp", sharpen),
149
+ entry("w", width),
150
+ ]
151
+ .filter((entry) => entry != null)
152
+ .map((entry) => entry.join("="))
153
+ .join("&")
154
+ );
155
+ }
@@ -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/params.ts DELETED
@@ -1,118 +0,0 @@
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/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
- }