srcdev-nuxt-components 9.1.45 → 9.1.47

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.
@@ -24,6 +24,7 @@ For arbitrary slot content — a grid of images, video, markup — use `ScrollRe
24
24
  | `parallaxOffset` | `string` | `"36rem"` | Distance the image travels vertically across the full scroll range. Larger = more dramatic reveal. |
25
25
  | `focalX` | `string` | `"50%"` | Horizontal focal point — CSS `object-position` x-axis value. Controls which horizontal slice stays in view. |
26
26
  | `radius` | `string` | `"0px"` | `border-radius` applied to the clipping frame. |
27
+ | `loading` | `"lazy" \| "eager"` | `"lazy"` | Image loading strategy. Use `"eager"` if this is the LCP image (e.g. partially in view on load). |
27
28
  | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root `<figure>`. |
28
29
 
29
30
  ## Basic usage
@@ -157,7 +158,7 @@ See `scroll-reveal-frame.md` for the full guide. For portrait images the default
157
158
  ## Notes
158
159
 
159
160
  - The root `<figure>` has `margin: 0` set in the component — browser default `<figure>` margins are neutralised at source.
160
- - `loading="lazy"` and `decoding="async"` are hardcoded on the `<img>`. If this component is the LCP image, override with `loading="eager"` via a CSS-only approach is not possible use `ScrollRevealFrame` with a manual `NuxtImg` instead and set `:loading="'eager'"`.
161
+ - `decoding="async"` is always set on the `<img>`. Use `:loading="'eager'"` if this component is the LCP image (e.g. a hero partially in view on load) the default `"lazy"` is correct for below-fold usage.
161
162
  - Do not place inside a container with `overflow: hidden` or `overflow: clip` — breaks the `view-timeline` scroll detection inherited from `ScrollRevealFrame`.
162
163
  - Reduced-motion: animation is disabled and the image falls back to a static crop centred at `object-position: <focalX> 50%`.
163
164
  - Storybook: the `"none"` image provider is active (`nuxt.config.ts`), so `src` paths pass through unchanged. Always provide explicit `img-width` and `img-height` props to avoid the `w=1536` fallback in deployed Storybook.
@@ -12,7 +12,7 @@
12
12
  :alt="alt"
13
13
  :width="imgWidth"
14
14
  :height="imgHeight"
15
- loading="lazy"
15
+ :loading="loading"
16
16
  decoding="async"
17
17
  />
18
18
  </ScrollRevealFrame>
@@ -51,6 +51,7 @@ interface Props {
51
51
  focalX?: string;
52
52
  /** Optional rounded corners on the frame. */
53
53
  radius?: string;
54
+ loading?: "lazy" | "eager";
54
55
  styleClassPassthrough?: string | string[];
55
56
  }
56
57
 
@@ -62,6 +63,7 @@ withDefaults(defineProps<Props>(), {
62
63
  parallaxOffset: "36rem",
63
64
  focalX: "50%",
64
65
  radius: "0px",
66
+ loading: "lazy",
65
67
  styleClassPassthrough: () => [],
66
68
  });
67
69
  </script>
@@ -32,6 +32,7 @@ describe("ScrollRevealImage", () => {
32
32
  parallaxOffset: "20rem",
33
33
  focalX: "30%",
34
34
  radius: "1.6rem",
35
+ loading: "eager",
35
36
  styleClassPassthrough: ["custom-class"],
36
37
  },
37
38
  });
@@ -179,13 +180,20 @@ describe("ScrollRevealImage", () => {
179
180
  expect(img.attributes("height")).toBe("1920");
180
181
  });
181
182
 
182
- it("sets loading=lazy on the image element", async () => {
183
+ it("sets loading=lazy on the image element by default", async () => {
183
184
  const wrapper = await mountSuspended(ScrollRevealImage, {
184
185
  props: { src: "/images/test.jpg" },
185
186
  });
186
187
  expect(wrapper.find("img[data-nuxt-img]").attributes("loading")).toBe("lazy");
187
188
  });
188
189
 
190
+ it("passes loading=eager to the image element when set", async () => {
191
+ const wrapper = await mountSuspended(ScrollRevealImage, {
192
+ props: { src: "/images/test.jpg", loading: "eager" },
193
+ });
194
+ expect(wrapper.find("img[data-nuxt-img]").attributes("loading")).toBe("eager");
195
+ });
196
+
189
197
  it("sets decoding=async on the image element", async () => {
190
198
  const wrapper = await mountSuspended(ScrollRevealImage, {
191
199
  props: { src: "/images/test.jpg" },
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports[`ScrollRevealImage > renders correct HTML structure (all props set) 1`] = `
4
4
  "<figure class="reveal-frame custom-class" style="--_frame-height: 400px; --_parallax-offset: 20rem; --_radius: 1.6rem; --_focal-x: 30%;">
5
- <div class="reveal-content"><img width="800" height="600" data-nuxt-img="" srcset="/_ipx/s_800x600/images/test.jpg 1x, /_ipx/s_1600x1200/images/test.jpg 2x" class="reveal-image" alt="All props" loading="lazy" decoding="async" src="/_ipx/s_800x600/images/test.jpg"></div>
5
+ <div class="reveal-content"><img width="800" height="600" data-nuxt-img="" srcset="/_ipx/s_800x600/images/test.jpg 1x, /_ipx/s_1600x1200/images/test.jpg 2x" class="reveal-image" alt="All props" loading="eager" decoding="async" src="/_ipx/s_800x600/images/test.jpg"></div>
6
6
  </figure>"
7
7
  `;
8
8
 
@@ -100,11 +100,6 @@ watch(
100
100
  }
101
101
 
102
102
  .profile-info {
103
- display: flex;
104
- flex-direction: column;
105
- gap: 1.5rem;
106
- height: stretch;
107
-
108
103
  .profile-info-content {
109
104
  .profile-info-block {
110
105
  margin-block-end: 1.5rem;
@@ -172,72 +172,80 @@ onUnmounted(stop);
172
172
  src: url("/fonts/monoMMM_5.ttf") format("truetype");
173
173
  font-weight: normal;
174
174
  font-style: normal;
175
- font-display: swap;
175
+ font-display: optional;
176
176
  }
177
177
 
178
- .samaritan-prompt {
179
- --_font-size: var(--samaritan-font-size, 2rem);
180
- --_color-text: var(--samaritan-color-text, #ffffff);
181
- --_color-underline: var(--samaritan-color-underline, #ffffff);
182
- --_color-cursor: var(--samaritan-color-cursor, #cc0000);
183
- --_color-cursor-off: var(--samaritan-color-cursor-off, transparent);
184
- --_font-family: var(--samaritan-font-family, "Mono MMM 5", "Nova Mono", "Courier New", monospace);
185
- --_letter-spacing: var(--samaritan-letter-spacing, 0.08em);
186
-
178
+ .samaritan-stage {
187
179
  display: flex;
188
- flex-direction: column;
189
180
  align-items: center;
190
- row-gap: 0.6rem;
191
- font-family: var(--_font-family);
192
- font-size: var(--_font-size);
193
- letter-spacing: var(--_letter-spacing);
181
+ justify-content: center;
182
+ min-block-size: 100dvh;
183
+ width: 100%;
184
+
185
+ .samaritan-prompt {
186
+ --_font-size: var(--samaritan-font-size, 2rem);
187
+ --_color-text: var(--samaritan-color-text, #ffffff);
188
+ --_color-underline: var(--samaritan-color-underline, #ffffff);
189
+ --_color-cursor: var(--samaritan-color-cursor, #cc0000);
190
+ --_color-cursor-off: var(--samaritan-color-cursor-off, transparent);
191
+ --_font-family: var(--samaritan-font-family, "Mono MMM 5", "Nova Mono", "Courier New", monospace);
192
+ --_letter-spacing: var(--samaritan-letter-spacing, 0.08em);
194
193
 
195
- .samaritan-prompt__content {
196
194
  display: flex;
197
195
  flex-direction: column;
198
196
  align-items: center;
199
197
  row-gap: 0.6rem;
200
- width: 100%;
201
- transition: opacity v-bind(fadeDurationCss) ease;
198
+ font-family: var(--_font-family);
199
+ font-size: var(--_font-size);
200
+ letter-spacing: var(--_letter-spacing);
202
201
 
203
- .samaritan-prompt__stage {
202
+ .samaritan-prompt__content {
204
203
  display: flex;
205
- justify-content: center;
206
- min-height: 1.2em;
204
+ flex-direction: column;
205
+ align-items: center;
206
+ row-gap: 0.6rem;
207
+ width: 100%;
208
+ transition: opacity v-bind(fadeDurationCss) ease;
209
+
210
+ .samaritan-prompt__stage {
211
+ display: flex;
212
+ justify-content: center;
213
+ min-height: 1.2em;
207
214
 
208
- .samaritan-prompt__text {
209
- color: var(--_color-text);
210
- white-space: nowrap;
211
- text-transform: uppercase;
215
+ .samaritan-prompt__text {
216
+ color: var(--_color-text);
217
+ white-space: nowrap;
218
+ text-transform: uppercase;
219
+ }
212
220
  }
213
- }
214
221
 
215
- .samaritan-prompt__underline {
216
- width: 100%;
217
- min-width: 4ch;
218
- height: 0.15rem;
219
- background: var(--_color-underline);
222
+ .samaritan-prompt__underline {
223
+ width: 100%;
224
+ min-width: 4ch;
225
+ height: 0.15rem;
226
+ background: var(--_color-underline);
227
+ }
220
228
  }
221
- }
222
229
 
223
- .samaritan-prompt__cursor {
224
- color: var(--_color-cursor);
225
- font-size: 2.4rem;
226
- line-height: 1;
227
- animation: samaritan-pulse 2.5s ease-in-out infinite;
228
- transition: opacity 400ms ease;
229
- }
230
+ .samaritan-prompt__cursor {
231
+ color: var(--_color-cursor);
232
+ font-size: 2.4rem;
233
+ line-height: 1;
234
+ animation: samaritan-pulse 2.5s ease-in-out infinite;
235
+ transition: opacity 400ms ease;
236
+ }
230
237
 
231
- .samaritan-prompt__sr-text {
232
- position: absolute;
233
- width: 1px;
234
- height: 1px;
235
- padding: 0;
236
- margin: -1px;
237
- overflow: hidden;
238
- clip: rect(0, 0, 0, 0);
239
- white-space: nowrap;
240
- border: 0;
238
+ .samaritan-prompt__sr-text {
239
+ position: absolute;
240
+ width: 1px;
241
+ height: 1px;
242
+ padding: 0;
243
+ margin: -1px;
244
+ overflow: hidden;
245
+ clip: rect(0, 0, 0, 0);
246
+ white-space: nowrap;
247
+ border: 0;
248
+ }
241
249
  }
242
250
  }
243
251
 
@@ -230,11 +230,6 @@ const qaHideCursorInCycle = ref(true);
230
230
  --samaritan-letter-spacing: 0.14em;
231
231
 
232
232
  .samaritan-stage {
233
- display: flex;
234
- align-items: center;
235
- justify-content: center;
236
- min-block-size: 100dvh;
237
-
238
233
  .samaritan-stage--mixed {
239
234
  border-top: 1px solid oklch(100% 0 0 / 0.08);
240
235
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.45",
4
+ "version": "9.1.47",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",