@teamnovu/nuxt-image 0.5.4 → 1.0.0-beta.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/README.md +229 -24
- package/dist/module.d.mts +70 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +67 -0
- package/dist/runtime/components/NovuBunnyImage.d.vue.ts +11 -0
- package/dist/runtime/components/NovuBunnyImage.vue +67 -0
- package/dist/runtime/components/NovuBunnyImage.vue.d.ts +11 -0
- package/dist/runtime/components/NovuCloudinaryImage.d.vue.ts +19 -0
- package/dist/runtime/components/NovuCloudinaryImage.vue +79 -0
- package/dist/runtime/components/NovuCloudinaryImage.vue.d.ts +19 -0
- package/dist/runtime/components/NovuImage.d.vue.ts +28 -0
- package/dist/runtime/components/NovuImage.vue +90 -0
- package/dist/runtime/components/NovuImage.vue.d.ts +28 -0
- package/dist/runtime/components/NovuImgproxyImage.d.vue.ts +12 -0
- package/dist/runtime/components/NovuImgproxyImage.vue +68 -0
- package/dist/runtime/components/NovuImgproxyImage.vue.d.ts +12 -0
- package/dist/runtime/components/NovuStatamicImage.d.vue.ts +17 -0
- package/dist/runtime/components/NovuStatamicImage.vue +87 -0
- package/dist/runtime/components/NovuStatamicImage.vue.d.ts +17 -0
- package/dist/runtime/composables/useResponsiveImage.d.ts +42 -0
- package/dist/runtime/composables/useResponsiveImage.js +115 -0
- package/dist/runtime/providers/imgproxy.d.ts +68 -0
- package/dist/runtime/providers/imgproxy.js +161 -0
- package/dist/runtime/types.d.ts +56 -0
- package/dist/runtime/types.js +0 -0
- package/dist/runtime/utils/focal.d.ts +11 -0
- package/dist/runtime/utils/focal.js +33 -0
- package/dist/runtime/utils/i18n.d.ts +1 -0
- package/dist/runtime/utils/i18n.js +10 -0
- package/dist/runtime/utils/numbers.d.ts +1 -0
- package/dist/runtime/utils/numbers.js +6 -0
- package/dist/runtime/utils/providerModifiers.d.ts +4 -0
- package/dist/runtime/utils/providerModifiers.js +36 -0
- package/dist/runtime/utils/screens.d.ts +5 -0
- package/dist/runtime/utils/screens.js +19 -0
- package/dist/runtime/utils/statamic.d.ts +3 -0
- package/dist/runtime/utils/statamic.js +143 -0
- package/dist/types.d.mts +3 -0
- package/package.json +65 -68
- package/CHANGELOG.md +0 -371
- package/LICENSE +0 -21
- package/dist/module.js +0 -482
- package/dist/runtime/components/image.mixin.d.ts +0 -46
- package/dist/runtime/components/image.mixin.js +0 -58
- package/dist/runtime/components/nuxt-img.vue +0 -49
- package/dist/runtime/components/nuxt-img.vue.d.ts +0 -12
- package/dist/runtime/components/nuxt-picture.vue +0 -86
- package/dist/runtime/components/nuxt-picture.vue.d.ts +0 -15
- package/dist/runtime/image.d.ts +0 -2
- package/dist/runtime/image.js +0 -194
- package/dist/runtime/index.d.ts +0 -2
- package/dist/runtime/index.js +0 -2
- package/dist/runtime/ipx.d.ts +0 -3
- package/dist/runtime/ipx.js +0 -3
- package/dist/runtime/plugin.d.ts +0 -1
- package/dist/runtime/plugin.js +0 -31
- package/dist/runtime/providers/cloudinary.d.ts +0 -2
- package/dist/runtime/providers/cloudinary.js +0 -96
- package/dist/runtime/providers/fastly.d.ts +0 -2
- package/dist/runtime/providers/fastly.js +0 -21
- package/dist/runtime/providers/glide.d.ts +0 -2
- package/dist/runtime/providers/glide.js +0 -49
- package/dist/runtime/providers/imagekit.d.ts +0 -2
- package/dist/runtime/providers/imagekit.js +0 -172
- package/dist/runtime/providers/imgix.d.ts +0 -3
- package/dist/runtime/providers/imgix.js +0 -153
- package/dist/runtime/providers/ipx.d.ts +0 -4
- package/dist/runtime/providers/ipx.js +0 -28
- package/dist/runtime/providers/netlify.d.ts +0 -3
- package/dist/runtime/providers/netlify.js +0 -40
- package/dist/runtime/providers/prismic.d.ts +0 -2
- package/dist/runtime/providers/prismic.js +0 -10
- package/dist/runtime/providers/sanity.d.ts +0 -2
- package/dist/runtime/providers/sanity.js +0 -87
- package/dist/runtime/providers/static.d.ts +0 -3
- package/dist/runtime/providers/static.js +0 -6
- package/dist/runtime/providers/storyblok.d.ts +0 -2
- package/dist/runtime/providers/storyblok.js +0 -27
- package/dist/runtime/providers/twicpics.d.ts +0 -2
- package/dist/runtime/providers/twicpics.js +0 -58
- package/dist/runtime/providers/vercel.d.ts +0 -3
- package/dist/runtime/providers/vercel.js +0 -28
- package/dist/runtime/utils/index.d.ts +0 -17
- package/dist/runtime/utils/index.js +0 -72
- package/dist/runtime/utils/meta.d.ts +0 -2
- package/dist/runtime/utils/meta.js +0 -67
- package/dist/runtime/utils/static-map.d.ts +0 -2
- package/dist/runtime/utils/static-map.js +0 -20
- package/dist/types.d.ts +0 -172
- package/vetur/attributes.json +0 -32
- package/vetur/tags.json +0 -32
package/README.md
CHANGED
|
@@ -1,35 +1,240 @@
|
|
|
1
|
-
|
|
1
|
+
# @teamnovu/nuxt-image
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
-
[![Checks][checks-src]][checks-href]
|
|
6
|
-
[![Codecov][codecov-src]][codecov-href]
|
|
3
|
+
Responsive images for Nuxt 4. Builds on [`@nuxt/image`](https://image.nuxt.com) for provider-agnostic URL generation, ships a JS-driven `sizes` strategy for accurate per-element widths, and includes a Statamic adapter so `<NovuStatamicImage :src="asset" />` handles the common case end-to-end.
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
- [▶️ Play online](https://githubbox.com/nuxt/image/tree/main/example)
|
|
5
|
+
## Highlights
|
|
10
6
|
|
|
7
|
+
- Single-image element with provider-agnostic responsive `srcset`.
|
|
8
|
+
- `sizes` starts at `1px` (so SSR ships a tiny blurred placeholder candidate) and is upgraded client-side to the actual rendered element width in `px`, recomputed via `ResizeObserver`. For `object-fit: cover` images that are wider than their container, the value reflects the cropped image's true width (`containerHeight * imageAspect`) so the browser picks a candidate big enough to fill the visible area without upscaling.
|
|
9
|
+
- Five components, layered:
|
|
10
|
+
- `<NovuImage>` - provider-agnostic base
|
|
11
|
+
- `<NovuCloudinaryImage>` - Cloudinary-flavoured (`focal: 'face'`, `zoom`, `crop`, raw `transforms`)
|
|
12
|
+
- `<NovuBunnyImage>` - Bunny-flavoured (`faceCrop`, `cropGravity`, `sharpen`, `blur`)
|
|
13
|
+
- `<NovuImgproxyImage>` - imgproxy-flavoured (`preset`, `gravity`, `resizingType`, `extend`)
|
|
14
|
+
- `<NovuStatamicImage>` - Statamic adapter that auto-resolves to the matching wrapper (or `<NovuImage>` when no wrapper matches)
|
|
15
|
+
- Built-in imgproxy provider (vendored from [nuxt/image#2117](https://github.com/nuxt/image/pull/2117) until it merges) with HMAC URL signing.
|
|
16
|
+
- Performance defaults: `loading="lazy"`, `decoding="async"`, inline `aspect-ratio` CSS for CLS prevention. Opt-in `priority` flips to `loading="eager"` + `fetchpriority="high"`.
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
## Install
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @teamnovu/nuxt-image
|
|
22
|
+
```
|
|
17
23
|
|
|
24
|
+
```ts
|
|
25
|
+
export default defineNuxtConfig({
|
|
26
|
+
modules: ['@teamnovu/nuxt-image'],
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
novuImage: {
|
|
29
|
+
provider: 'cloudinary',
|
|
30
|
+
},
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
image: {
|
|
33
|
+
cloudinary: {
|
|
34
|
+
baseURL: 'https://res.cloudinary.com/your-cloud/image/upload/',
|
|
35
|
+
},
|
|
36
|
+
// bunny: { baseURL: 'https://your-zone.b-cdn.net' },
|
|
37
|
+
// imgproxy: { baseURL: 'https://imgproxy.example.com', key: '...', salt: '...' },
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
```
|
|
22
41
|
|
|
42
|
+
The module installs `@nuxt/image` for you and auto-registers the imgproxy provider. Configure `image.<provider>` exactly as documented in the [`@nuxt/image` docs](https://image.nuxt.com/get-started/configuration).
|
|
23
43
|
|
|
44
|
+
## Module options (`novuImage`)
|
|
24
45
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
[
|
|
46
|
+
| Option | Type | Default | Notes |
|
|
47
|
+
| -------------------- | ------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------ |
|
|
48
|
+
| `screens` | `Record<string, number\|string>` | `{ xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1600, '3xl': 2000 }` | Each entry generates one `srcset` candidate at that pixel width. |
|
|
49
|
+
| `fallbackWidth` | `number` | `2000` | Width used for the legacy `src` URL. |
|
|
50
|
+
| `usePlaceholder` | `boolean` | `true` | Inlines a low-quality candidate (descriptor `32w`) into `srcset`. |
|
|
51
|
+
| `placeholderQuality` | `number` | `30` | Quality of the inlined placeholder. |
|
|
52
|
+
| `placeholderWidth` | `number` | `300` | Width of the inlined placeholder. |
|
|
53
|
+
| `defaultQuality` | `number\|string?` | - | Optional global `quality` modifier. Leave unset to defer to the provider's own default (Cloudinary auto-applies `q_auto`, etc.). |
|
|
54
|
+
| `defaultFormat` | `string?` | - | Optional global `format` modifier. Leave unset to defer to the provider's own default (Cloudinary auto-applies `f_auto`, imgproxy uses `webp`, etc.). |
|
|
55
|
+
| `provider` | `string?` | - | Default `@nuxt/image` provider; also drives `<NovuStatamicImage>` wrapper resolution. |
|
|
56
|
+
| `statamicPlaceholderFields` | `string[]` | `['placeholder', 'lqip', 'thumbhash']` | Statamic asset field handles checked for `daun/statamic-placeholders` data. |
|
|
57
|
+
|
|
58
|
+
## Components
|
|
59
|
+
|
|
60
|
+
### `<NovuImage>` - provider-agnostic base
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<NovuImage
|
|
64
|
+
src="path/to/image.jpg"
|
|
65
|
+
alt="A cat"
|
|
66
|
+
:width="1600"
|
|
67
|
+
:height="900"
|
|
68
|
+
:focal="[1200, 660]"
|
|
69
|
+
provider="cloudinary"
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Common props (all optional unless noted):
|
|
74
|
+
|
|
75
|
+
| Prop | Type | Notes |
|
|
76
|
+
| ------------- | ----------------------------- | -------------------------------------------------------------------- |
|
|
77
|
+
| `src` | `string` (required) | Asset key/path passed to the configured provider. |
|
|
78
|
+
| `alt` | `string` | Alt text. |
|
|
79
|
+
| `width/height`| `number\|string` | Rendered image dimensions. Used for the `aspect-ratio` CSS hint and candidate height derivation. |
|
|
80
|
+
| `sourceWidth/sourceHeight` | `number\|string` | Original source dimensions. Used to map source-pixel focal points for providers such as imgproxy when rendered dimensions differ from the source. |
|
|
81
|
+
| `aspectRatio` | `number` | Explicit aspect ratio (overrides `width/height`). |
|
|
82
|
+
| `focal` | `'auto' \| [number, number]` | Source-pixel focal point, translated per provider. Providers with native auto focal support default to `'auto'`. |
|
|
83
|
+
| `provider` | `string` | Override the default provider for this element. |
|
|
84
|
+
| `quality` | `number\|string` | Forwarded to `useImage`. |
|
|
85
|
+
| `format` | `string` | Forwarded to `useImage`. |
|
|
86
|
+
| `fit` | `string` | Forwarded to `useImage`. |
|
|
87
|
+
| `modifiers` | `Record<string, unknown>` | Raw modifiers escape hatch. |
|
|
88
|
+
| `sizes` | `string` | Hard override for the dynamic `sizes` calculation. |
|
|
89
|
+
| `priority` | `boolean` | Sets `loading="eager"` + `fetchpriority="high"`. |
|
|
90
|
+
| `loading` | `'lazy' \| 'eager'` | Defaults to `'lazy'` (or `'eager'` when `priority`). |
|
|
91
|
+
| `decoding` | `'async' \| 'sync' \| 'auto'` | Defaults to `'async'`. |
|
|
92
|
+
|
|
93
|
+
`focal` tuples are source image pixel coordinates, for example `[1200, 660]` on a `4000x2667` source. When the rendered `width/height` differ from the source dimensions, pass `sourceWidth/sourceHeight` as well so providers that need normalized coordinates, such as imgproxy, can map the focal point correctly. `<NovuStatamicImage>` fills these source dimensions from the asset automatically. Cloudinary and imgproxy default to their native auto focal modes; Bunny does not, because its `face_crop` API is a separate explicit behavior.
|
|
94
|
+
|
|
95
|
+
### `<NovuCloudinaryImage>`
|
|
96
|
+
|
|
97
|
+
Pins `provider="cloudinary"` and adds Cloudinary-only conveniences. Most importantly, `focal="face"` triggers Cloudinary's face-detection gravity.
|
|
98
|
+
|
|
99
|
+
| Prop | Type | Notes |
|
|
100
|
+
| ---------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
101
|
+
| `crop` | `string` | Cloudinary crop mode (URL parameter `c_`). Accepts both Cloudinary-native names (`'lfill'`, `'thumb'`) and `@nuxt/image`'s aliases (`'cover'`, `'thumbnail'`). |
|
|
102
|
+
| `focal` | `'auto' \| 'face' \| [x, y]` | Adds `'face'` on top of the agnostic `focal` values. |
|
|
103
|
+
| `zoom` | `number \| string` | Cloudinary zoom factor (`z_<value>`). |
|
|
104
|
+
| `transformation` | `string \| Record<string, any>` | Named Cloudinary transformation preset (`t_<name>`), or a raw modifiers object spread into `useImage`'s modifiers. |
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<NovuCloudinaryImage
|
|
108
|
+
src="profile.jpg"
|
|
109
|
+
alt="Profile photo"
|
|
110
|
+
:width="800"
|
|
111
|
+
:height="800"
|
|
112
|
+
focal="face"
|
|
113
|
+
crop="thumb"
|
|
114
|
+
zoom="0.7"
|
|
115
|
+
/>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `<NovuBunnyImage>`
|
|
119
|
+
|
|
120
|
+
Pins `provider="bunny"`. `faceCrop` maps to Bunny's face-detection `face_crop` parameter, while `cropGravity` maps directly to Bunny's documented `crop_gravity` anchors (`center`, `north`, `south`, `east`, `west`, `northeast`, `northwest`, `southeast`, `southwest`). Pixel-coordinate focal points are ignored because Bunny's manual focal crop is not wired yet.
|
|
121
|
+
|
|
122
|
+
```vue
|
|
123
|
+
<NovuBunnyImage
|
|
124
|
+
src="hero.jpg"
|
|
125
|
+
:width="1600"
|
|
126
|
+
:height="900"
|
|
127
|
+
face-crop
|
|
128
|
+
:sharpen="true"
|
|
129
|
+
/>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `<NovuImgproxyImage>`
|
|
133
|
+
|
|
134
|
+
Pins `provider="imgproxy"`. Use `gravity` to pass an imgproxy-native gravity string (e.g. `'fp:0.3:0.25'`); when `gravity` is set the agnostic `focal` mapping is bypassed for that element.
|
|
135
|
+
|
|
136
|
+
```vue
|
|
137
|
+
<NovuImgproxyImage
|
|
138
|
+
src="https://origin.example.com/photo.jpg"
|
|
139
|
+
:width="1600"
|
|
140
|
+
:height="900"
|
|
141
|
+
preset="hero"
|
|
142
|
+
resizing-type="fill"
|
|
143
|
+
/>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `<NovuStatamicImage>` - the 90% case
|
|
147
|
+
|
|
148
|
+
Renders any Statamic image asset. Auto-resolves to the matching provider wrapper (`provider="cloudinary"` -> `<NovuCloudinaryImage>`, etc.) and falls back to `<NovuImage>` when no wrapper matches.
|
|
149
|
+
|
|
150
|
+
```vue
|
|
151
|
+
<NovuStatamicImage :src="entry.hero_image" />
|
|
152
|
+
<NovuStatamicImage :src="entry.hero_image" provider="bunny" />
|
|
153
|
+
<NovuStatamicImage :src="user.avatar" :fallback-alt="user.name" />
|
|
154
|
+
<NovuStatamicImage :src="entry.hero_image" placeholder-field="hero_lqip" />
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Alt-text resolution order
|
|
158
|
+
|
|
159
|
+
1. `props.alt` (hard override).
|
|
160
|
+
2. `asset.alt_${locale}` if a locale is detected from `useNuxtApp().$i18n.locale` or set via the `locale` prop.
|
|
161
|
+
3. `asset.alt`.
|
|
162
|
+
4. `props.fallbackAlt` (use this for things like `user.name`).
|
|
163
|
+
5. `undefined`.
|
|
164
|
+
|
|
165
|
+
#### Focal-point resolution order
|
|
166
|
+
|
|
167
|
+
1. `props.focal` (hard override).
|
|
168
|
+
2. `asset.focus` (Statamic's `"x-y"` percent format).
|
|
169
|
+
3. `asset.focus_css` (Statamic's `"x% y%"` format - some sites add this via augmentation).
|
|
170
|
+
4. `undefined` (provider falls back to its own default - usually centre).
|
|
171
|
+
|
|
172
|
+
The dead-centre default (`focus: '50-50'`) is treated as "no focal point" so providers can pick their own smarter centre/strategy.
|
|
173
|
+
|
|
174
|
+
#### Statamic placeholders
|
|
175
|
+
|
|
176
|
+
`<NovuStatamicImage>` can consume placeholder data generated by [`daun/statamic-placeholders`](https://github.com/daun/statamic-placeholders). By default it checks the `placeholder`, `lqip`, and `thumbhash` asset fields; override the checked handles globally with `novuImage.statamicPlaceholderFields` or per component with `placeholderField`.
|
|
177
|
+
|
|
178
|
+
Query the ready-to-use data URI when payload size is not a concern:
|
|
179
|
+
|
|
180
|
+
```graphql
|
|
181
|
+
... on Asset_Assets {
|
|
182
|
+
alt
|
|
183
|
+
lqip {
|
|
184
|
+
uri
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Or query only the ThumbHash hash and let the Nuxt component decode it:
|
|
190
|
+
|
|
191
|
+
```graphql
|
|
192
|
+
... on Asset_Assets {
|
|
193
|
+
alt
|
|
194
|
+
lqip {
|
|
195
|
+
type
|
|
196
|
+
hash
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
When both `uri` and `hash` are present, `uri` wins. Hash decoding is supported for ThumbHash placeholders only; BlurHash or average-color placeholders need to provide `uri`. If a Statamic site does not have the addon or the queried asset object has no placeholder field, rendering falls back to the existing generated low-quality `srcset` placeholder. Only include placeholder subfields in GraphQL queries for Statamic schemas where the addon field exists, because GraphQL itself rejects unknown fields before this component can handle the missing data.
|
|
202
|
+
|
|
203
|
+
#### Statamic blueprint requirements
|
|
204
|
+
|
|
205
|
+
The `<NovuStatamicImage>` component reads the following fields from a Statamic asset object:
|
|
206
|
+
|
|
207
|
+
- `permalink` (required) - absolute URL; the host is stripped so the configured provider's `baseURL` applies.
|
|
208
|
+
- `width` / `height` - required to convert `focus` percentages to pixel coordinates (and to compute `aspect-ratio`).
|
|
209
|
+
- `alt` - default alt text.
|
|
210
|
+
- `alt_<locale>` (e.g. `alt_de`, `alt_fr`) - localised alt text. Required only if you want per-locale alts.
|
|
211
|
+
- `focus` - Statamic's native `"x-y"` percent string. Optional.
|
|
212
|
+
- `placeholder` / `lqip` / `thumbhash` - optional placeholder fields from `daun/statamic-placeholders`, either as `{ uri }` or `{ type, hash }`.
|
|
213
|
+
|
|
214
|
+
## Composable
|
|
215
|
+
|
|
216
|
+
`resolveStatamicAsset(src, props?, options?)` is a pure helper exposed for cases where you want to compute the resolved `{ src, placeholderSrc, alt, focal, width, height, aspectRatio }` outside of the component (for SEO heads, OG images, etc.).
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { resolveStatamicAsset } from '#imports'
|
|
220
|
+
|
|
221
|
+
const resolved = resolveStatamicAsset(asset, { fallbackAlt: 'Article hero' }, { locale: 'de' })
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## A note on the imgproxy provider
|
|
225
|
+
|
|
226
|
+
The imgproxy provider used here is vendored from [nuxt/image#2117](https://github.com/nuxt/image/pull/2117). When that PR merges into `@nuxt/image`, the vendored version (and the `@noble/hashes` runtime dependency) will be removed.
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pnpm dev:prepare # build types + prepare playground
|
|
232
|
+
pnpm dev # run the playground
|
|
233
|
+
pnpm test # run vitest
|
|
234
|
+
pnpm lint # run eslint
|
|
235
|
+
pnpm test:types # vue-tsc on src/ and playground/
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
|
|
3
|
+
interface ModuleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Breakpoint widths used to build the `srcset`. Each entry generates one
|
|
6
|
+
* candidate at that pixel width. Plain numbers or strings with `px` suffix.
|
|
7
|
+
*
|
|
8
|
+
* @default { xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1600, '3xl': 2000 }
|
|
9
|
+
*/
|
|
10
|
+
screens: Record<string, number | string>;
|
|
11
|
+
/**
|
|
12
|
+
* Width used for the legacy `src` URL (browsers that ignore `srcset`/`sizes`).
|
|
13
|
+
* @default 2000
|
|
14
|
+
*/
|
|
15
|
+
fallbackWidth: number;
|
|
16
|
+
/**
|
|
17
|
+
* Whether to inline a low-quality blurred placeholder into the `srcset`
|
|
18
|
+
* (descriptor `32w`) so the smallest candidate is shown until the real image loads.
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
usePlaceholder: boolean;
|
|
22
|
+
/** Quality of the inlined placeholder. @default 30 */
|
|
23
|
+
placeholderQuality: number;
|
|
24
|
+
/** Width of the inlined placeholder in px. @default 300 */
|
|
25
|
+
placeholderWidth: number;
|
|
26
|
+
/**
|
|
27
|
+
* Optional global `quality` modifier forwarded to `useImage`. Leave unset
|
|
28
|
+
* to defer to the provider's own default (Cloudinary auto-applies `q_auto`,
|
|
29
|
+
* imgproxy uses its own configured quality, etc.).
|
|
30
|
+
*/
|
|
31
|
+
defaultQuality?: number | string;
|
|
32
|
+
/**
|
|
33
|
+
* Optional global `format` modifier forwarded to `useImage`. Leave unset
|
|
34
|
+
* to defer to the provider's own default (Cloudinary auto-applies `f_auto`,
|
|
35
|
+
* imgproxy defaults to `webp`, etc.).
|
|
36
|
+
*/
|
|
37
|
+
defaultFormat?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Default provider name forwarded to `useImage`. Also used by
|
|
40
|
+
* `<NovuStatamicImage>` to auto-resolve the matching wrapper component.
|
|
41
|
+
*/
|
|
42
|
+
provider?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Statamic asset field handles checked for daun/statamic-placeholders data.
|
|
45
|
+
* Each field may be a data URI string or a GraphQL object with `uri`, `type`,
|
|
46
|
+
* and `hash` subfields.
|
|
47
|
+
*
|
|
48
|
+
* @default ['placeholder', 'lqip', 'thumbhash']
|
|
49
|
+
*/
|
|
50
|
+
statamicPlaceholderFields: string[];
|
|
51
|
+
}
|
|
52
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
53
|
+
|
|
54
|
+
declare module '@nuxt/schema' {
|
|
55
|
+
interface PublicRuntimeConfig {
|
|
56
|
+
novuImage: ModuleOptions;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
declare module '@nuxt/image' {
|
|
60
|
+
interface ImageProviders {
|
|
61
|
+
imgproxy: {
|
|
62
|
+
baseURL: string;
|
|
63
|
+
key: string;
|
|
64
|
+
salt: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { _default as default };
|
|
70
|
+
export type { ModuleOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, installModule, addComponent, addImports, addImportsDir } from '@nuxt/kit';
|
|
2
|
+
import { defu } from 'defu';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_SCREENS = {
|
|
5
|
+
"xs": 320,
|
|
6
|
+
"sm": 640,
|
|
7
|
+
"md": 768,
|
|
8
|
+
"lg": 1024,
|
|
9
|
+
"xl": 1280,
|
|
10
|
+
"2xl": 1600,
|
|
11
|
+
"3xl": 2e3
|
|
12
|
+
};
|
|
13
|
+
const DEFAULTS = {
|
|
14
|
+
screens: DEFAULT_SCREENS,
|
|
15
|
+
fallbackWidth: 2e3,
|
|
16
|
+
usePlaceholder: true,
|
|
17
|
+
placeholderQuality: 30,
|
|
18
|
+
placeholderWidth: 300,
|
|
19
|
+
statamicPlaceholderFields: ["placeholder", "lqip", "thumbhash"]
|
|
20
|
+
};
|
|
21
|
+
const module$1 = defineNuxtModule({
|
|
22
|
+
meta: {
|
|
23
|
+
name: "@teamnovu/nuxt-image",
|
|
24
|
+
configKey: "novuImage",
|
|
25
|
+
compatibility: {
|
|
26
|
+
nuxt: ">=3.13.0"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
defaults: DEFAULTS,
|
|
30
|
+
async setup(options, nuxt) {
|
|
31
|
+
const resolver = createResolver(import.meta.url);
|
|
32
|
+
const imageOptions = nuxt.options.image && typeof nuxt.options.image === "object" ? nuxt.options.image : {};
|
|
33
|
+
nuxt.options.image = imageOptions;
|
|
34
|
+
const providers = imageOptions.providers ??= {};
|
|
35
|
+
if (!providers.imgproxy) {
|
|
36
|
+
const imgproxyOptions = imageOptions.imgproxy;
|
|
37
|
+
providers.imgproxy = {
|
|
38
|
+
provider: resolver.resolve("./runtime/providers/imgproxy"),
|
|
39
|
+
...imgproxyOptions ? { options: imgproxyOptions } : {}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
await installModule("@nuxt/image", imageOptions);
|
|
43
|
+
const publicConfig = nuxt.options.runtimeConfig.public;
|
|
44
|
+
publicConfig.novuImage = defu(publicConfig.novuImage, options);
|
|
45
|
+
for (const name of [
|
|
46
|
+
"NovuImage",
|
|
47
|
+
"NovuCloudinaryImage",
|
|
48
|
+
"NovuBunnyImage",
|
|
49
|
+
"NovuImgproxyImage",
|
|
50
|
+
"NovuStatamicImage"
|
|
51
|
+
]) {
|
|
52
|
+
addComponent({
|
|
53
|
+
name,
|
|
54
|
+
filePath: resolver.resolve(`./runtime/components/${name}.vue`),
|
|
55
|
+
global: true
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
addImports({
|
|
59
|
+
name: "resolveStatamicAsset",
|
|
60
|
+
from: resolver.resolve("./runtime/utils/statamic")
|
|
61
|
+
});
|
|
62
|
+
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
63
|
+
nuxt.options.alias["#novu-image"] = resolver.resolve("./runtime");
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NovuImageProps } from './NovuImage.vue.js';
|
|
2
|
+
export type BunnyCropGravity = 'center' | 'north' | 'south' | 'east' | 'west' | 'northeast' | 'northwest' | 'southeast' | 'southwest';
|
|
3
|
+
export interface NovuBunnyImageProps extends Omit<NovuImageProps, 'provider'> {
|
|
4
|
+
cropGravity?: BunnyCropGravity;
|
|
5
|
+
faceCrop?: boolean | string;
|
|
6
|
+
sharpen?: boolean | number;
|
|
7
|
+
blur?: number;
|
|
8
|
+
}
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<NovuBunnyImageProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NovuBunnyImageProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import NovuImage from "./NovuImage.vue";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
cropGravity: { type: String, required: false, default: void 0 },
|
|
6
|
+
faceCrop: { type: [Boolean, String], required: false, default: void 0 },
|
|
7
|
+
sharpen: { type: [Boolean, Number], required: false, default: void 0 },
|
|
8
|
+
blur: { type: Number, required: false, default: void 0 },
|
|
9
|
+
src: { type: String, required: true },
|
|
10
|
+
alt: { type: String, required: false },
|
|
11
|
+
width: { type: [Number, String], required: false },
|
|
12
|
+
height: { type: [Number, String], required: false },
|
|
13
|
+
sourceWidth: { type: [Number, String], required: false },
|
|
14
|
+
sourceHeight: { type: [Number, String], required: false },
|
|
15
|
+
aspectRatio: { type: Number, required: false },
|
|
16
|
+
quality: { type: [Number, String], required: false },
|
|
17
|
+
format: { type: String, required: false },
|
|
18
|
+
fit: { type: String, required: false },
|
|
19
|
+
focal: { type: [String, Array], required: false },
|
|
20
|
+
modifiers: { type: Object, required: false },
|
|
21
|
+
sizes: { type: String, required: false },
|
|
22
|
+
loading: { type: String, required: false },
|
|
23
|
+
decoding: { type: String, required: false },
|
|
24
|
+
priority: { type: Boolean, required: false },
|
|
25
|
+
fallbackWidth: { type: Number, required: false },
|
|
26
|
+
usePlaceholder: { type: Boolean, required: false },
|
|
27
|
+
placeholderQuality: { type: Number, required: false },
|
|
28
|
+
placeholderWidth: { type: Number, required: false },
|
|
29
|
+
placeholderSrc: { type: String, required: false }
|
|
30
|
+
});
|
|
31
|
+
const bunnyModifiers = computed(() => {
|
|
32
|
+
const out = { ...props.modifiers ?? {} };
|
|
33
|
+
if (props.cropGravity !== void 0) out.cropGravity = props.cropGravity;
|
|
34
|
+
if (props.faceCrop !== void 0 && props.faceCrop !== false) out.faceCrop = props.faceCrop;
|
|
35
|
+
if (props.sharpen !== void 0) out.sharpen = props.sharpen;
|
|
36
|
+
if (props.blur !== void 0) out.blur = props.blur;
|
|
37
|
+
return out;
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<NovuImage
|
|
43
|
+
v-bind="$attrs"
|
|
44
|
+
:src="src"
|
|
45
|
+
:alt="alt"
|
|
46
|
+
:width="width"
|
|
47
|
+
:height="height"
|
|
48
|
+
:source-width="sourceWidth"
|
|
49
|
+
:source-height="sourceHeight"
|
|
50
|
+
:aspect-ratio="aspectRatio"
|
|
51
|
+
:quality="quality"
|
|
52
|
+
:format="format"
|
|
53
|
+
:fit="fit"
|
|
54
|
+
:focal="focal"
|
|
55
|
+
:modifiers="bunnyModifiers"
|
|
56
|
+
:sizes="sizes"
|
|
57
|
+
:loading="loading"
|
|
58
|
+
:decoding="decoding"
|
|
59
|
+
:priority="priority"
|
|
60
|
+
:fallback-width="fallbackWidth"
|
|
61
|
+
:use-placeholder="usePlaceholder"
|
|
62
|
+
:placeholder-quality="placeholderQuality"
|
|
63
|
+
:placeholder-width="placeholderWidth"
|
|
64
|
+
:placeholder-src="placeholderSrc"
|
|
65
|
+
provider="bunny"
|
|
66
|
+
/>
|
|
67
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NovuImageProps } from './NovuImage.vue.js';
|
|
2
|
+
export type BunnyCropGravity = 'center' | 'north' | 'south' | 'east' | 'west' | 'northeast' | 'northwest' | 'southeast' | 'southwest';
|
|
3
|
+
export interface NovuBunnyImageProps extends Omit<NovuImageProps, 'provider'> {
|
|
4
|
+
cropGravity?: BunnyCropGravity;
|
|
5
|
+
faceCrop?: boolean | string;
|
|
6
|
+
sharpen?: boolean | number;
|
|
7
|
+
blur?: number;
|
|
8
|
+
}
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<NovuBunnyImageProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NovuBunnyImageProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NovuImageProps } from './NovuImage.vue.js';
|
|
2
|
+
import type { FocalPoint } from '../utils/focal.js';
|
|
3
|
+
export type CloudinaryFocal = 'auto' | 'face' | FocalPoint;
|
|
4
|
+
export interface NovuCloudinaryImageProps extends Omit<NovuImageProps, 'focal' | 'provider'> {
|
|
5
|
+
focal?: CloudinaryFocal;
|
|
6
|
+
zoom?: number | string;
|
|
7
|
+
/**
|
|
8
|
+
* Cloudinary crop mode (URL parameter `c_`). Accepts both Cloudinary-native
|
|
9
|
+
* values (`'lfill'`, `'thumb'`, `'fill'`, `'pad'`, ...) and `@nuxt/image`'s
|
|
10
|
+
* normalised aliases (`'cover'`, `'contain'`, `'thumbnail'`, ...). Maps to
|
|
11
|
+
* the `fit` modifier under the hood.
|
|
12
|
+
*/
|
|
13
|
+
crop?: string;
|
|
14
|
+
/** Named Cloudinary transformation preset (URL parameter `t_<name>`), or a raw modifiers object spread into `useImage`'s modifiers. */
|
|
15
|
+
transformation?: string | Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
declare const __VLS_export: import("vue").DefineComponent<NovuCloudinaryImageProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NovuCloudinaryImageProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import NovuImage from "./NovuImage.vue";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
focal: { type: [String, Array], required: false, default: void 0 },
|
|
6
|
+
zoom: { type: [Number, String], required: false, default: void 0 },
|
|
7
|
+
crop: { type: String, required: false, default: void 0 },
|
|
8
|
+
transformation: { type: [String, Object], required: false, default: void 0 },
|
|
9
|
+
src: { type: String, required: true },
|
|
10
|
+
alt: { type: String, required: false },
|
|
11
|
+
width: { type: [Number, String], required: false },
|
|
12
|
+
height: { type: [Number, String], required: false },
|
|
13
|
+
sourceWidth: { type: [Number, String], required: false },
|
|
14
|
+
sourceHeight: { type: [Number, String], required: false },
|
|
15
|
+
aspectRatio: { type: Number, required: false },
|
|
16
|
+
quality: { type: [Number, String], required: false },
|
|
17
|
+
format: { type: String, required: false },
|
|
18
|
+
fit: { type: String, required: false },
|
|
19
|
+
modifiers: { type: Object, required: false },
|
|
20
|
+
sizes: { type: String, required: false },
|
|
21
|
+
loading: { type: String, required: false },
|
|
22
|
+
decoding: { type: String, required: false },
|
|
23
|
+
priority: { type: Boolean, required: false },
|
|
24
|
+
fallbackWidth: { type: Number, required: false },
|
|
25
|
+
usePlaceholder: { type: Boolean, required: false },
|
|
26
|
+
placeholderQuality: { type: Number, required: false },
|
|
27
|
+
placeholderWidth: { type: Number, required: false },
|
|
28
|
+
placeholderSrc: { type: String, required: false }
|
|
29
|
+
});
|
|
30
|
+
const cloudinaryFocal = computed(() => {
|
|
31
|
+
if (props.focal === "face") return void 0;
|
|
32
|
+
return props.focal;
|
|
33
|
+
});
|
|
34
|
+
const effectiveFit = computed(() => props.crop ?? props.fit);
|
|
35
|
+
const cloudinaryModifiers = computed(() => {
|
|
36
|
+
const out = { ...props.modifiers ?? {} };
|
|
37
|
+
if (props.zoom !== void 0) out.zoom = props.zoom;
|
|
38
|
+
if (props.focal === "face") {
|
|
39
|
+
out.gravity = "face";
|
|
40
|
+
if (props.crop === void 0 && out.fit === void 0) out.fit = "fill";
|
|
41
|
+
}
|
|
42
|
+
if (props.transformation) {
|
|
43
|
+
if (typeof props.transformation === "string") {
|
|
44
|
+
out.transformation = props.transformation;
|
|
45
|
+
} else {
|
|
46
|
+
Object.assign(out, props.transformation);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
});
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<NovuImage
|
|
55
|
+
v-bind="$attrs"
|
|
56
|
+
:src="src"
|
|
57
|
+
:alt="alt"
|
|
58
|
+
:width="width"
|
|
59
|
+
:height="height"
|
|
60
|
+
:source-width="sourceWidth"
|
|
61
|
+
:source-height="sourceHeight"
|
|
62
|
+
:aspect-ratio="aspectRatio"
|
|
63
|
+
:quality="quality"
|
|
64
|
+
:format="format"
|
|
65
|
+
:fit="effectiveFit"
|
|
66
|
+
:focal="cloudinaryFocal"
|
|
67
|
+
:modifiers="cloudinaryModifiers"
|
|
68
|
+
:sizes="sizes"
|
|
69
|
+
:loading="loading"
|
|
70
|
+
:decoding="decoding"
|
|
71
|
+
:priority="priority"
|
|
72
|
+
:fallback-width="fallbackWidth"
|
|
73
|
+
:use-placeholder="usePlaceholder"
|
|
74
|
+
:placeholder-quality="placeholderQuality"
|
|
75
|
+
:placeholder-width="placeholderWidth"
|
|
76
|
+
:placeholder-src="placeholderSrc"
|
|
77
|
+
provider="cloudinary"
|
|
78
|
+
/>
|
|
79
|
+
</template>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NovuImageProps } from './NovuImage.vue.js';
|
|
2
|
+
import type { FocalPoint } from '../utils/focal.js';
|
|
3
|
+
export type CloudinaryFocal = 'auto' | 'face' | FocalPoint;
|
|
4
|
+
export interface NovuCloudinaryImageProps extends Omit<NovuImageProps, 'focal' | 'provider'> {
|
|
5
|
+
focal?: CloudinaryFocal;
|
|
6
|
+
zoom?: number | string;
|
|
7
|
+
/**
|
|
8
|
+
* Cloudinary crop mode (URL parameter `c_`). Accepts both Cloudinary-native
|
|
9
|
+
* values (`'lfill'`, `'thumb'`, `'fill'`, `'pad'`, ...) and `@nuxt/image`'s
|
|
10
|
+
* normalised aliases (`'cover'`, `'contain'`, `'thumbnail'`, ...). Maps to
|
|
11
|
+
* the `fit` modifier under the hood.
|
|
12
|
+
*/
|
|
13
|
+
crop?: string;
|
|
14
|
+
/** Named Cloudinary transformation preset (URL parameter `t_<name>`), or a raw modifiers object spread into `useImage`'s modifiers. */
|
|
15
|
+
transformation?: string | Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
declare const __VLS_export: import("vue").DefineComponent<NovuCloudinaryImageProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NovuCloudinaryImageProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FocalPoint } from '../utils/focal.js';
|
|
2
|
+
export interface NovuImageProps {
|
|
3
|
+
src: string;
|
|
4
|
+
alt?: string;
|
|
5
|
+
width?: number | string;
|
|
6
|
+
height?: number | string;
|
|
7
|
+
sourceWidth?: number | string;
|
|
8
|
+
sourceHeight?: number | string;
|
|
9
|
+
aspectRatio?: number;
|
|
10
|
+
quality?: number | string;
|
|
11
|
+
format?: string;
|
|
12
|
+
fit?: string;
|
|
13
|
+
focal?: FocalPoint;
|
|
14
|
+
modifiers?: Record<string, unknown>;
|
|
15
|
+
provider?: string;
|
|
16
|
+
sizes?: string;
|
|
17
|
+
loading?: 'lazy' | 'eager';
|
|
18
|
+
decoding?: 'async' | 'sync' | 'auto';
|
|
19
|
+
priority?: boolean;
|
|
20
|
+
fallbackWidth?: number;
|
|
21
|
+
usePlaceholder?: boolean;
|
|
22
|
+
placeholderQuality?: number;
|
|
23
|
+
placeholderWidth?: number;
|
|
24
|
+
placeholderSrc?: string;
|
|
25
|
+
}
|
|
26
|
+
declare const __VLS_export: import("vue").DefineComponent<NovuImageProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<NovuImageProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
27
|
+
declare const _default: typeof __VLS_export;
|
|
28
|
+
export default _default;
|