sanity-plugin-image-field 0.0.1
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/LICENSE +21 -0
- package/README.md +404 -0
- package/dist/index.cjs +1512 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +145 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.js +1513 -0
- package/dist/index.js.map +1 -0
- package/package.json +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Corey Ward
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# sanity-plugin-image-field
|
|
2
|
+
|
|
3
|
+
A custom image input for the Sanity Studio that gives you control over image
|
|
4
|
+
cropping and dimensions. Unlike Sanity's built-in image field, this plugin lets
|
|
5
|
+
you:
|
|
6
|
+
|
|
7
|
+
- **Constrain aspect ratios** — Force crops into specific ratios (like 16:9 for
|
|
8
|
+
hero images) or ranges (like 4:3 to 16:9 for flexible layouts)
|
|
9
|
+
- **Enforce dimension requirements** — Ensure cropped images meet minimum size
|
|
10
|
+
requirements for quality or performance
|
|
11
|
+
- **Provide real-time feedback** — Show content editors warnings when crops are
|
|
12
|
+
too small, and block saving when they don't meet requirements
|
|
13
|
+
|
|
14
|
+
## How it works
|
|
15
|
+
|
|
16
|
+
This plugin integrates seamlessly with Sanity's existing image system:
|
|
17
|
+
|
|
18
|
+
- It replaces the UI component in the Studio responsible for uploading images
|
|
19
|
+
with a custom one that has cropping and validation built in
|
|
20
|
+
- It can also add custom validators to the field definition that will be
|
|
21
|
+
enforced by the Studio
|
|
22
|
+
- Crops and hotspot values are stored on the field in the standard `crop` and
|
|
23
|
+
`hotspot` properties
|
|
24
|
+
- Any front-end code using
|
|
25
|
+
[`@sanity/image-url`](https://www.sanity.io/docs/image-url),
|
|
26
|
+
[`@sanity-image/url-builder`](https://github.com/sanity-io/image-url),
|
|
27
|
+
[`sanity-image`](https://github.com/coreyward/sanity-image) or other crop +
|
|
28
|
+
hotspot respecting tooling applies the crop automatically without any
|
|
29
|
+
front-end changes
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm install sanity-plugin-image-field
|
|
35
|
+
# or
|
|
36
|
+
pnpm add sanity-plugin-image-field
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This plugin also requires `@sanity/ui`. If you don't already have it:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
npm install @sanity/ui
|
|
43
|
+
# or:
|
|
44
|
+
pnpm add @sanity/ui
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
The fastest way to get started is using `defineImageField`, which sets up the
|
|
50
|
+
field with validation automatically:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { defineType } from "sanity"
|
|
54
|
+
import { defineImageField } from "sanity-plugin-image-field"
|
|
55
|
+
|
|
56
|
+
export const page = defineType({
|
|
57
|
+
name: "page",
|
|
58
|
+
type: "document",
|
|
59
|
+
fields: [
|
|
60
|
+
defineImageField({
|
|
61
|
+
name: "hero",
|
|
62
|
+
title: "Hero image",
|
|
63
|
+
// Constrain crop to landscape aspect ratios between 4:3 and 16:9
|
|
64
|
+
aspectRatio: { min: 4 / 3, max: 16 / 9 },
|
|
65
|
+
// Warn editors if crop is smaller than this
|
|
66
|
+
recommendedDimensions: { width: 1600, height: 900 },
|
|
67
|
+
// Block crops smaller than this (and validate at document level)
|
|
68
|
+
requiredDimensions: { width: 800, height: 450 },
|
|
69
|
+
validation: (Rule) => Rule.required(),
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Choosing an approach
|
|
76
|
+
|
|
77
|
+
This plugin provides a React component (`ImageFieldInput`) that replaces
|
|
78
|
+
Sanity's default image input. To use it, you need to:
|
|
79
|
+
|
|
80
|
+
1. **Specify the component** — Tell Sanity when to use `ImageFieldInput` instead
|
|
81
|
+
of the default
|
|
82
|
+
2. **Configure the constraints** — Set aspect ratio and dimension options that
|
|
83
|
+
the component reads
|
|
84
|
+
|
|
85
|
+
### How to specify the component
|
|
86
|
+
|
|
87
|
+
You have two ways to tell Sanity to use `ImageFieldInput`:
|
|
88
|
+
|
|
89
|
+
**1. Per-field (explicit)** Specify `ImageFieldInput` as the input component on
|
|
90
|
+
individual image fields where you want to use it:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { defineField } from "sanity"
|
|
94
|
+
import { ImageFieldInput } from "sanity-plugin-image-field"
|
|
95
|
+
|
|
96
|
+
defineField({
|
|
97
|
+
name: "hero",
|
|
98
|
+
type: "image",
|
|
99
|
+
components: { input: ImageFieldInput }, // ← Use our component here
|
|
100
|
+
options: {
|
|
101
|
+
imageField: {
|
|
102
|
+
aspectRatio: 16 / 9,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**2. Studio-wide default (implicit)** Register `imageFieldFormInput` as the
|
|
109
|
+
default input component for all image fields in your Sanity config:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// sanity.config.ts
|
|
113
|
+
import { defineConfig } from "sanity"
|
|
114
|
+
import { imageFieldFormInput } from "sanity-plugin-image-field"
|
|
115
|
+
|
|
116
|
+
export default defineConfig({
|
|
117
|
+
// ...
|
|
118
|
+
form: { components: { input: imageFieldFormInput } }, // ← Use for all image fields
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### How configuration works
|
|
123
|
+
|
|
124
|
+
Regardless of how you specify the component, configuration always happens the
|
|
125
|
+
same way: through `options.imageField` on your image field definition. The
|
|
126
|
+
`ImageFieldInput` component reads these options to determine how to constrain
|
|
127
|
+
cropping:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
defineField({
|
|
131
|
+
name: "hero",
|
|
132
|
+
type: "image",
|
|
133
|
+
options: {
|
|
134
|
+
imageField: {
|
|
135
|
+
// ← Configuration lives here
|
|
136
|
+
aspectRatio: 16 / 9,
|
|
137
|
+
recommendedDimensions: { width: 1600, height: 900 },
|
|
138
|
+
requiredDimensions: { width: 800, height: 450 },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### The `defineImageField` helper
|
|
145
|
+
|
|
146
|
+
`defineImageField` is a convenience function that:
|
|
147
|
+
|
|
148
|
+
- Uses the **per-field** approach to specify `ImageFieldInput`
|
|
149
|
+
- Adds your constraints to `options.imageField` automatically
|
|
150
|
+
- Builds document-level validation from your dimension requirements
|
|
151
|
+
- Merges any additional validation you provide
|
|
152
|
+
|
|
153
|
+
It's compatible with both per-field specification and studio-wide defaults.
|
|
154
|
+
|
|
155
|
+
### Which approach should you use?
|
|
156
|
+
|
|
157
|
+
### Use `defineImageField` when:
|
|
158
|
+
|
|
159
|
+
- You want the simplest setup with built-in validation
|
|
160
|
+
- You're adding image fields to individual schemas
|
|
161
|
+
- You want document-level validation that prevents saving invalid crops
|
|
162
|
+
|
|
163
|
+
**This is the recommended approach for most use cases.**
|
|
164
|
+
|
|
165
|
+
### Use manual per-field configuration when:
|
|
166
|
+
|
|
167
|
+
- You need fine-grained control over the field definition
|
|
168
|
+
- You want to add custom validation logic alongside the image constraints
|
|
169
|
+
- You're integrating with existing field configurations
|
|
170
|
+
- You prefer the explicit, visible approach of specifying components per field
|
|
171
|
+
|
|
172
|
+
### Use the studio-wide default when:
|
|
173
|
+
|
|
174
|
+
- You want ALL image fields in your Studio to use this plugin
|
|
175
|
+
- You have many image fields and don't want to configure each one individually
|
|
176
|
+
- You want to apply the same constraints globally
|
|
177
|
+
- You'll combine it with `defineImageField` where you need document-level
|
|
178
|
+
validation
|
|
179
|
+
|
|
180
|
+
## Configuration options
|
|
181
|
+
|
|
182
|
+
All configuration lives under `options.imageField` in your field definition:
|
|
183
|
+
|
|
184
|
+
| Option | Type | Description |
|
|
185
|
+
| ----------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
186
|
+
| `aspectRatio` | `number \| { min; max } \| number[]` | Allowed crop ratios as `width / height`. Use a single number for a fixed ratio, a `{ min, max }` range for flexibility, or an array of specific ratios to snap between. |
|
|
187
|
+
| `recommendedDimensions` | `{ width; height }` | Shows a non-blocking warning when the crop is smaller than this size. Helps editors maintain quality standards. |
|
|
188
|
+
| `requiredDimensions` | `{ width; height }` | Blocks confirmation when the crop is smaller than this size. With `defineImageField`, also adds document-level validation. |
|
|
189
|
+
|
|
190
|
+
### Aspect ratio examples
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// Fixed ratio (e.g., for hero images that must be exactly 16:9)
|
|
194
|
+
aspectRatio: 16 / 9
|
|
195
|
+
|
|
196
|
+
// Flexible range (e.g., for cards that can be 4:3 to 16:9)
|
|
197
|
+
aspectRatio: { min: 4 / 3, max: 16 / 9 }
|
|
198
|
+
|
|
199
|
+
// Discrete options (e.g., for social media images that must be specific ratios)
|
|
200
|
+
aspectRatio: [16 / 9, 4 / 3, 1] // Snaps to nearest ratio as you drag
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Usage patterns
|
|
204
|
+
|
|
205
|
+
### 1. Recommended: Using `defineImageField`
|
|
206
|
+
|
|
207
|
+
This helper creates a fully configured image field with automatic validation:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { defineType } from "sanity"
|
|
211
|
+
import { defineImageField } from "sanity-plugin-image-field"
|
|
212
|
+
|
|
213
|
+
export const page = defineType({
|
|
214
|
+
name: "page",
|
|
215
|
+
type: "document",
|
|
216
|
+
fields: [
|
|
217
|
+
defineImageField({
|
|
218
|
+
name: "hero",
|
|
219
|
+
title: "Hero image",
|
|
220
|
+
aspectRatio: { min: 4 / 3, max: 16 / 9 },
|
|
221
|
+
recommendedDimensions: { width: 1600, height: 900 },
|
|
222
|
+
requiredDimensions: { width: 800, height: 450 },
|
|
223
|
+
validation: (Rule) => Rule.required(),
|
|
224
|
+
}),
|
|
225
|
+
],
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Benefits:**
|
|
230
|
+
|
|
231
|
+
- Simplest API — all configuration in one place
|
|
232
|
+
- Automatic document-level validation from dimension constraints
|
|
233
|
+
- Type-safe and integrates with Sanity's field system
|
|
234
|
+
|
|
235
|
+
### 2. Manual field configuration
|
|
236
|
+
|
|
237
|
+
For more control, specify `ImageFieldInput` as the component:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { defineField, defineType } from "sanity"
|
|
241
|
+
import { ImageFieldInput } from "sanity-plugin-image-field"
|
|
242
|
+
|
|
243
|
+
export const page = defineType({
|
|
244
|
+
name: "page",
|
|
245
|
+
type: "document",
|
|
246
|
+
fields: [
|
|
247
|
+
defineField({
|
|
248
|
+
name: "hero",
|
|
249
|
+
title: "Hero image",
|
|
250
|
+
type: "image",
|
|
251
|
+
options: {
|
|
252
|
+
hotspot: true, // Enable focal point editor (Sanity's standard option)
|
|
253
|
+
imageField: {
|
|
254
|
+
aspectRatio: { min: 4 / 3, max: 16 / 9 },
|
|
255
|
+
recommendedDimensions: { width: 1600, height: 900 },
|
|
256
|
+
requiredDimensions: { width: 800, height: 450 },
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
components: { input: ImageFieldInput },
|
|
260
|
+
}),
|
|
261
|
+
],
|
|
262
|
+
})
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Benefits:**
|
|
266
|
+
|
|
267
|
+
- Full control over field definition
|
|
268
|
+
- Can add custom validation logic
|
|
269
|
+
- Integrates with existing field configurations
|
|
270
|
+
|
|
271
|
+
**Note:** This approach doesn't automatically add document-level validation for
|
|
272
|
+
dimensions. Add it manually if needed, or use `defineImageField`.
|
|
273
|
+
|
|
274
|
+
### 3. Form-level default
|
|
275
|
+
|
|
276
|
+
Apply this input to all image fields in your Studio:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
// sanity.config.ts
|
|
280
|
+
import { defineConfig } from "sanity"
|
|
281
|
+
import { imageFieldFormInput } from "sanity-plugin-image-field"
|
|
282
|
+
|
|
283
|
+
export default defineConfig({
|
|
284
|
+
// ... other config
|
|
285
|
+
form: { components: { input: imageFieldFormInput } },
|
|
286
|
+
})
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Then configure individual fields through `options.imageField`:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
defineField({
|
|
293
|
+
name: "hero",
|
|
294
|
+
type: "image",
|
|
295
|
+
options: {
|
|
296
|
+
imageField: {
|
|
297
|
+
aspectRatio: 16 / 9,
|
|
298
|
+
requiredDimensions: { width: 800, height: 450 },
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
})
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Benefits:**
|
|
305
|
+
|
|
306
|
+
- Consistent behavior across all image fields
|
|
307
|
+
- Don't need to specify the component on each field
|
|
308
|
+
- Combine with `defineImageField` where you need validation
|
|
309
|
+
|
|
310
|
+
## Editor experience
|
|
311
|
+
|
|
312
|
+
### Upload
|
|
313
|
+
|
|
314
|
+
Content editors can upload images by:
|
|
315
|
+
|
|
316
|
+
- Using the file picker
|
|
317
|
+
- Dragging and dropping an image onto the field
|
|
318
|
+
- Pasting an image from the clipboard
|
|
319
|
+
- Selecting an existing asset from the Media library or other configured sources
|
|
320
|
+
|
|
321
|
+
The field honors any `options.accept` format restrictions you set.
|
|
322
|
+
|
|
323
|
+
### Cropping
|
|
324
|
+
|
|
325
|
+
When an aspect ratio constraint is configured, the crop editor opens
|
|
326
|
+
automatically after upload. The editor:
|
|
327
|
+
|
|
328
|
+
- Defaults to the largest crop that fits your constraint
|
|
329
|
+
- Shows live pixel dimensions with warnings for size requirements
|
|
330
|
+
- Blocks confirmation while the crop is below the required size
|
|
331
|
+
- Supports keyboard controls (arrow keys to move, Shift+arrow to resize)
|
|
332
|
+
- Supports modifier keys:
|
|
333
|
+
- **Shift** while dragging a corner to lock the current aspect ratio
|
|
334
|
+
- **Option/Alt** while dragging to resize about the center
|
|
335
|
+
- When using an array of aspect ratios, snaps to the nearest ratio as you drag
|
|
336
|
+
(never showing in-between ratios)
|
|
337
|
+
|
|
338
|
+
### Focal point
|
|
339
|
+
|
|
340
|
+
When you enable `options.hotspot: true`, editors can set a focal point for the
|
|
341
|
+
image. This is stored in Sanity's standard `hotspot` field and works with
|
|
342
|
+
front-end tools that support cover-mode framing. The focal point marker is
|
|
343
|
+
positioned relative to the crop area.
|
|
344
|
+
|
|
345
|
+
### Confirmed state
|
|
346
|
+
|
|
347
|
+
After cropping, editors see:
|
|
348
|
+
|
|
349
|
+
- The cropped image preview
|
|
350
|
+
- Buttons to re-crop, replace, select a different asset, or remove the image
|
|
351
|
+
- Any dimension warnings based on your configuration
|
|
352
|
+
|
|
353
|
+
## How cropping works with Sanity
|
|
354
|
+
|
|
355
|
+
This plugin uses Sanity's standard, non-destructive cropping system:
|
|
356
|
+
|
|
357
|
+
- **Storage format** — Crops are stored as `{ top, bottom, left, right }` insets
|
|
358
|
+
(0-1 range) on the standard `crop` field
|
|
359
|
+
- **Original preservation** — Your original images are never modified
|
|
360
|
+
- **Automatic application** — Front-end code using
|
|
361
|
+
[`@sanity/image-url`](https://www.sanity.io/docs/image-url),
|
|
362
|
+
[`@sanity-image/url-builder`](https://github.com/sanity-io/image-url), or
|
|
363
|
+
[`sanity-image`](https://github.com/coreyward/sanity-image) applies the crop
|
|
364
|
+
automatically
|
|
365
|
+
- **Dimension calculation** — Cropped dimensions are computed from the original
|
|
366
|
+
asset's metadata, so no additional API calls are needed
|
|
367
|
+
|
|
368
|
+
### SVG handling
|
|
369
|
+
|
|
370
|
+
SVGs are handled specially since Sanity does not support crop transformations on
|
|
371
|
+
SVG images:
|
|
372
|
+
|
|
373
|
+
- Uploads work normally
|
|
374
|
+
- The crop dialog is not shown for SVG images
|
|
375
|
+
- Aspect ratio and dimension validation are not performed on SVG images, even
|
|
376
|
+
when the SVG has dimensions (from `viewBox` or `width`/`height` attributes)
|
|
377
|
+
- The asset reference is still stored normally
|
|
378
|
+
|
|
379
|
+
## Playground
|
|
380
|
+
|
|
381
|
+
A runnable Sanity Studio is available in the `playground/` directory. To try it:
|
|
382
|
+
|
|
383
|
+
```sh
|
|
384
|
+
# Build the package (or run `pnpm dev` to watch for changes)
|
|
385
|
+
pnpm build
|
|
386
|
+
|
|
387
|
+
# Configure the playground with your Sanity project
|
|
388
|
+
cp playground/.env.example playground/.env
|
|
389
|
+
# Edit playground/.env and set SANITY_STUDIO_PROJECT_ID
|
|
390
|
+
|
|
391
|
+
# Start the playground
|
|
392
|
+
pnpm playground:dev
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Future work
|
|
396
|
+
|
|
397
|
+
- **SVG validation** — Add validation for SVG images that have dimensions (from
|
|
398
|
+
`viewBox` or `width`/`height` attributes) to check aspect ratio and minimum
|
|
399
|
+
dimensions. Currently, all SVG images bypass validation since Sanity does not
|
|
400
|
+
support crop transformations on SVGs.
|
|
401
|
+
|
|
402
|
+
## License
|
|
403
|
+
|
|
404
|
+
MIT
|