@page-speed/img 0.4.3 → 0.4.5

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/core/Img.cjs CHANGED
@@ -1,23 +1,13 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
4
- import { useOptimizedImage } from '@page-speed/hooks';
5
- import { DEFAULT_CDN_HOST, buildPlaceholderImageUrl, fetchImageData, imageVariantsHaveRenderableSource, } from '../utils/api.js';
6
- import { useMediaSelectionEffect } from './useMediaSelectionEffect.js';
7
- import { useResponsiveReset } from './useResponsiveReset.js';
8
- const DEFAULT_WIDTHS = {
9
- sm: 640,
10
- md: 1024,
11
- lg: 1536,
12
- full: 2560,
13
- };
14
- const MAX_VARIANT_REFRESH_ATTEMPTS = 5;
15
- const VARIANT_REFRESH_DELAY_MS = 3000;
16
- const TRANSPARENT_PIXEL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
3
+ import { forwardRef, memo, useCallback, useEffect, useMemo, useRef } from "react";
4
+ import { useOptimizedImage } from "@page-speed/hooks/media";
5
+ import { useMediaSelectionEffect } from "./useMediaSelectionEffect.js";
6
+ import { useResponsiveReset } from "./useResponsiveReset.js";
7
+ const TRANSPARENT_PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
17
8
  let defaultOptixFlowConfig;
18
- const deprecatedMediaWarnings = new Set();
19
9
  const readGlobalOptixFlowConfig = () => {
20
- if (typeof globalThis === 'undefined')
10
+ if (typeof globalThis === "undefined")
21
11
  return undefined;
22
12
  const globalAny = globalThis;
23
13
  return (globalAny.PageSpeedImgDefaults?.optixFlowConfig ||
@@ -30,24 +20,13 @@ const resolveOptixFlowConfig = (config) => {
30
20
  export const setDefaultOptixFlowConfig = (config) => {
31
21
  defaultOptixFlowConfig = config ?? undefined;
32
22
  };
33
- const warnDeprecatedMediaId = (mediaId) => {
34
- if (!Number.isFinite(mediaId) || mediaId == null)
35
- return;
36
- const id = mediaId;
37
- if (deprecatedMediaWarnings.has(id))
38
- return;
39
- deprecatedMediaWarnings.add(id);
40
- if (typeof console !== 'undefined' && console.warn) {
41
- console.warn('[DEPRECATED] <Img mediaId> is deprecated. Provide src + optixFlowConfig instead.', { mediaId: id });
42
- }
43
- };
44
- const isUrlString = (value) => typeof value === 'string' && value.trim().length > 0;
23
+ const isUrlString = (value) => typeof value === "string" && value.trim().length > 0;
45
24
  const parseDimension = (value) => {
46
- if (value === '' || value === null || typeof value === 'undefined')
25
+ if (value === "" || value === null || typeof value === "undefined")
47
26
  return undefined;
48
- if (typeof value === 'number' && Number.isFinite(value))
27
+ if (typeof value === "number" && Number.isFinite(value))
49
28
  return value;
50
- if (typeof value === 'string') {
29
+ if (typeof value === "string") {
51
30
  const numeric = Number(value);
52
31
  if (Number.isFinite(numeric))
53
32
  return numeric;
@@ -58,294 +37,93 @@ const composeRefs = (hookRef, forwardedRef, localRef) => useCallback((node) => {
58
37
  hookRef(node);
59
38
  // eslint-disable-next-line no-param-reassign
60
39
  localRef.current = node;
61
- if (typeof forwardedRef === 'function') {
40
+ if (typeof forwardedRef === "function") {
62
41
  forwardedRef(node);
63
42
  }
64
- else if (forwardedRef && typeof forwardedRef === 'object') {
43
+ else if (forwardedRef && typeof forwardedRef === "object") {
65
44
  forwardedRef.current = node;
66
45
  }
67
46
  }, [hookRef, forwardedRef, localRef]);
68
- function widthMapFromMetadata(v) {
69
- const w = v?.widths;
70
- if (!w)
71
- return null;
72
- return {
73
- sm: w.small ?? w.sm ?? DEFAULT_WIDTHS.sm,
74
- md: w.medium ?? w.md ?? DEFAULT_WIDTHS.md,
75
- lg: w.large ?? w.lg ?? DEFAULT_WIDTHS.lg,
76
- full: w.full_size ?? w.full ?? DEFAULT_WIDTHS.full,
77
- };
78
- }
79
- function pickBest(sizes) {
80
- if (!sizes)
81
- return undefined;
82
- return sizes.md || sizes.lg || sizes.sm || sizes.full || Object.values(sizes).find(Boolean);
83
- }
84
- const DEFAULT_SIZES = '(max-width:640px) 640px, (max-width:1024px) 1024px, 1536px';
85
47
  const ModernImg = ({ sizes, loading, decoding, alt, title, src: directSrc, eager, intersectionMargin, intersectionThreshold, optixFlowConfig, forwardedRef, ...rest }) => {
86
48
  const imgRef = useRef(null);
87
49
  const pictureRef = useRef(null);
50
+ const logKeyRef = useRef(null);
88
51
  useResponsiveReset(pictureRef);
89
52
  useMediaSelectionEffect();
90
- const normalizedSrc = useMemo(() => (typeof directSrc === 'string' ? directSrc.trim() : ''), [directSrc]);
53
+ const normalizedSrc = useMemo(() => (typeof directSrc === "string" ? directSrc.trim() : ""), [directSrc]);
91
54
  const numericWidth = useMemo(() => parseDimension(rest.width), [rest]);
92
55
  const numericHeight = useMemo(() => parseDimension(rest.height), [rest]);
93
56
  const resolvedOptixConfig = useMemo(() => resolveOptixFlowConfig(optixFlowConfig), [optixFlowConfig]);
94
- const eagerLoad = eager ?? loading === 'eager';
95
- const { ref: hookRef, src, srcset, sizes: computedSizes, loading: hookLoading, size } = useOptimizedImage({
57
+ const eagerLoad = eager ?? loading === "eager";
58
+ const { ref: hookRef, src, srcset, sizes: computedSizes, loading: hookLoading, isInView, size, } = useOptimizedImage({
96
59
  src: normalizedSrc,
97
60
  eager: eagerLoad,
98
61
  width: numericWidth,
99
62
  height: numericHeight,
100
- rootMargin: intersectionMargin ?? '200px',
63
+ rootMargin: intersectionMargin ?? "200px",
101
64
  threshold: intersectionThreshold ?? 0.1,
102
65
  optixFlowConfig: resolvedOptixConfig,
103
66
  });
104
67
  const mergedRef = composeRefs(hookRef, forwardedRef, imgRef);
105
68
  const { width, height, ...restProps } = rest;
106
69
  const sizesAttr = sizes ?? (computedSizes || undefined);
107
- const loadingAttr = loading ?? hookLoading ?? 'lazy';
108
- const decodingAttr = decoding ?? 'async';
70
+ const loadingAttr = loading ?? hookLoading ?? "lazy";
71
+ const decodingAttr = decoding ?? "async";
109
72
  const hasSrcSet = Boolean(srcset.avif || srcset.webp || srcset.jpeg);
110
73
  const imgSrc = src || normalizedSrc || TRANSPARENT_PIXEL;
111
- const inlineSrcSet = hasSrcSet && !srcset.avif && !srcset.webp ? srcset.jpeg : '';
74
+ const inlineSrcSet = hasSrcSet && !srcset.avif && !srcset.webp ? srcset.jpeg : "";
112
75
  const parsedWidth = parseDimension(width);
113
76
  const parsedHeight = parseDimension(height);
114
77
  const widthAttr = parsedWidth ?? (size.width || numericWidth || undefined);
115
78
  const heightAttr = parsedHeight ?? (size.height || numericHeight || undefined);
116
- if (!hasSrcSet) {
117
- return (_jsx("img", { ref: mergedRef, src: imgSrc, loading: loadingAttr, decoding: decodingAttr, alt: alt, title: title, width: widthAttr, height: heightAttr, ...restProps }));
118
- }
119
- return (_jsxs("picture", { ref: pictureRef, children: [srcset.avif ? _jsx("source", { type: "image/avif", srcSet: srcset.avif, sizes: sizesAttr }) : null, srcset.webp ? _jsx("source", { type: "image/webp", srcSet: srcset.webp, sizes: sizesAttr }) : null, _jsx("img", { ref: mergedRef, src: imgSrc, srcSet: inlineSrcSet || undefined, sizes: inlineSrcSet ? sizesAttr : undefined, loading: loadingAttr, decoding: decodingAttr, alt: alt, title: title, width: widthAttr, height: heightAttr, ...restProps })] }));
120
- };
121
- const LegacyImg = ({ mediaId, cdnHost, sizes, onImageData, loading, decoding, alt, title, src: directSrc, forwardedRef, ...rest }) => {
122
- const imgRef = useRef(null);
123
- const pictureRef = useRef(null);
124
- useImperativeHandle(forwardedRef, () => imgRef.current);
125
- useResponsiveReset(pictureRef);
126
- useMediaSelectionEffect();
127
- const [data, setData] = useState(null);
128
- const [retryCount, setRetryCount] = useState(0);
129
- const hasMediaId = Number.isFinite(mediaId);
130
- const loadingAttr = loading ?? 'lazy';
131
- const decodingAttr = decoding ?? 'async';
132
- const [isInView, setIsInView] = useState(() => !hasMediaId || loadingAttr !== 'lazy');
133
- const cdnOrigin = useMemo(() => (cdnHost ?? DEFAULT_CDN_HOST).replace(/\/$/, ''), [cdnHost]);
134
- useEffect(() => {
135
- if (!hasMediaId) {
136
- setData(null);
137
- setRetryCount(0);
138
- return;
139
- }
140
- setData(null);
141
- setRetryCount(0);
142
- }, [hasMediaId, mediaId, cdnHost]);
143
- useEffect(() => {
144
- if (!hasMediaId) {
145
- return;
146
- }
147
- const controller = new AbortController();
148
- fetchImageData(mediaId, {
149
- cdnHost,
150
- signal: controller.signal,
151
- bypassCache: retryCount > 0,
152
- })
153
- .then((d) => {
154
- setData(d);
155
- onImageData?.(d);
156
- })
157
- .catch((err) => {
158
- if (err?.name !== 'AbortError') {
159
- // eslint-disable-next-line no-console
160
- console.warn('Image data fetch failed:', err);
161
- }
162
- });
163
- return () => controller.abort();
164
- }, [hasMediaId, mediaId, cdnHost, onImageData, retryCount]);
79
+ // Temporary logging to detect repeated transform requests and URL churn.
165
80
  useEffect(() => {
166
- if (!hasMediaId || loadingAttr !== 'lazy') {
167
- setIsInView(true);
81
+ if (typeof window === "undefined")
168
82
  return;
169
- }
170
- setIsInView(false);
171
- }, [hasMediaId, mediaId, loadingAttr]);
172
- useEffect(() => {
173
- if (!hasMediaId || loadingAttr !== 'lazy' || isInView) {
83
+ if (!eagerLoad && !isInView)
174
84
  return;
175
- }
176
- if (typeof window === 'undefined' || typeof window.IntersectionObserver === 'undefined') {
177
- setIsInView(true);
85
+ if (!imgSrc || imgSrc === TRANSPARENT_PIXEL)
178
86
  return;
179
- }
180
- const node = imgRef.current;
181
- if (!node) {
87
+ const logKey = [
88
+ imgSrc,
89
+ srcset.avif,
90
+ srcset.webp,
91
+ srcset.jpeg,
92
+ sizesAttr ?? "",
93
+ ].join("|");
94
+ if (logKeyRef.current === logKey)
182
95
  return;
96
+ logKeyRef.current = logKey;
97
+ if (typeof console !== "undefined" && console.info) {
98
+ console.info("[PageSpeedImg] image request", {
99
+ src: imgSrc,
100
+ srcset,
101
+ sizes: sizesAttr,
102
+ });
183
103
  }
184
- const observer = new IntersectionObserver((entries) => {
185
- if (entries.some((entry) => entry.isIntersecting)) {
186
- setIsInView(true);
187
- observer.disconnect();
188
- }
189
- }, { rootMargin: '200px' });
190
- observer.observe(node);
191
- return () => observer.disconnect();
192
- }, [hasMediaId, loadingAttr, isInView]);
193
- // Build picture/source/srcset from variants
194
- const picture = useMemo(() => {
195
- if (!data)
196
- return null;
197
- const v = data.variants_data?.variants ?? {};
198
- const webp = v.WEBP;
199
- const avif = v.AVIF;
200
- const jpeg = v.JPEG;
201
- const widths = widthMapFromMetadata(v.WEBP?.metadata) ||
202
- widthMapFromMetadata(v.JPEG?.metadata) ||
203
- { ...DEFAULT_WIDTHS };
204
- const ensureAbsolute = (url) => {
205
- if (!isUrlString(url))
206
- return undefined;
207
- if (/^https?:\/\//i.test(url) || url.startsWith('data:'))
208
- return url;
209
- if (url.startsWith('//'))
210
- return `https:${url}`;
211
- if (url.startsWith('/'))
212
- return `${cdnOrigin}${url}`;
213
- return `${cdnOrigin}/${url}`;
214
- };
215
- const normalizeCandidate = (candidate) => ensureAbsolute(typeof candidate === 'string' ? candidate : undefined);
216
- const variantCandidates = [
217
- pickBest(webp),
218
- pickBest(jpeg),
219
- pickBest(avif),
220
- webp?.sm,
221
- webp?.md,
222
- webp?.lg,
223
- webp?.full,
224
- jpeg?.sm,
225
- jpeg?.md,
226
- jpeg?.lg,
227
- jpeg?.full,
228
- avif?.sm,
229
- avif?.md,
230
- avif?.lg,
231
- avif?.full,
232
- ]
233
- .map((candidate) => normalizeCandidate(candidate ?? undefined))
234
- .filter(isUrlString);
235
- const raw = data;
236
- const directCandidates = [
237
- raw.img_url,
238
- raw.file_data_url,
239
- raw.file_data_thumbnail_url,
240
- raw.img_src,
241
- raw.med_src,
242
- raw.thumb_src,
243
- raw.low_res_thumb,
244
- ]
245
- .map((candidate) => (isUrlString(candidate) ? normalizeCandidate(candidate) : undefined))
246
- .filter(isUrlString);
247
- // Add fallback_url as the final option if no variants or direct candidates
248
- const fallbackCandidates = raw.fallback_url ? [normalizeCandidate(raw.fallback_url)].filter(isUrlString) : [];
249
- const fallback = [...variantCandidates, ...directCandidates, ...fallbackCandidates][0];
250
- if (!fallback) {
251
- return null;
252
- }
253
- const toSrcSet = (sizes) => {
254
- if (!sizes)
255
- return undefined;
256
- const entries = [];
257
- const push = (url, width) => {
258
- const absolute = normalizeCandidate(url);
259
- if (absolute && width)
260
- entries.push(`${absolute} ${width}w`);
261
- };
262
- push(sizes.sm, widths.sm);
263
- push(sizes.md, widths.md);
264
- push(sizes.lg, widths.lg);
265
- push(sizes.full, widths.full);
266
- return entries.length ? entries.join(', ') : undefined;
267
- };
268
- return { webp, avif, jpeg, toSrcSet, fallback, widths, hasVariantSource: variantCandidates.length > 0 };
269
- }, [data, cdnOrigin]);
270
- const hasVariantEntries = useMemo(() => imageVariantsHaveRenderableSource(data?.variants_data?.variants ?? null), [data]);
271
- const variantsStatus = useMemo(() => {
272
- const status = (data?.variants_data?.status ?? data?.variants_status) ?? '';
273
- return typeof status === 'string' ? status.toLowerCase() : '';
274
- }, [data]);
275
- const variantsFailed = variantsStatus === 'failed' || variantsStatus === 'error';
276
- const shouldPollForVariants = hasMediaId && Boolean(data) && !variantsFailed && !hasVariantEntries && retryCount < MAX_VARIANT_REFRESH_ATTEMPTS;
277
- useEffect(() => {
278
- if (!shouldPollForVariants) {
279
- return;
280
- }
281
- if (typeof window === 'undefined') {
282
- return;
283
- }
284
- const timeoutId = window.setTimeout(() => {
285
- setRetryCount((count) => count + 1);
286
- }, VARIANT_REFRESH_DELAY_MS);
287
- return () => window.clearTimeout(timeoutId);
288
- }, [shouldPollForVariants]);
289
- // Map HTML attributes from content manifest and sizing
290
- const altAttr = useMemo(() => {
291
- if (typeof alt === 'string')
292
- return alt;
293
- return data?.meta?.content_manifest?.summary ?? undefined;
294
- }, [alt, data]);
295
- const titleAttr = useMemo(() => {
296
- if (typeof title === 'string')
297
- return title;
298
- return data?.meta?.content_manifest?.title ?? undefined;
299
- }, [title, data]);
300
- const widthAttr = useMemo(() => data?.meta?.sizing?.width ?? data?.variants_data?.metadata?.width ?? undefined, [data]);
301
- const heightAttr = useMemo(() => data?.meta?.sizing?.height ?? data?.variants_data?.metadata?.height ?? undefined, [data]);
302
- // Compute data-filename for consumers that need semantic filenames
303
- const dataFilename = useMemo(() => {
304
- const base = data?.meta?.content_manifest?.optimized_filename;
305
- if (!base)
306
- return undefined;
307
- // ext derived from chosen fallback url
308
- const href = picture?.fallback;
309
- if (!href)
310
- return undefined;
311
- const dot = href.lastIndexOf('.');
312
- const ext = dot > -1 ? href.slice(dot + 1).toLowerCase() : 'jpg';
313
- return `${base}.${ext}`;
314
- }, [data, picture]);
315
- // If mediaId not provided but src is, render plain img
316
- if (!hasMediaId) {
317
- const r = { ...rest };
318
- return (_jsx("img", { ref: imgRef, src: directSrc, loading: loadingAttr, decoding: decodingAttr, alt: altAttr, title: titleAttr, width: r.width, height: r.height, ...r }));
319
- }
320
- const placeholderSrc = buildPlaceholderImageUrl(mediaId, cdnHost);
321
- if (!data || !picture || !isInView) {
322
- const r = { ...rest };
323
- return (_jsx("img", { ref: imgRef, src: placeholderSrc, loading: loadingAttr, decoding: decodingAttr, alt: altAttr, title: titleAttr, width: r.width ?? widthAttr, height: r.height ?? heightAttr, ...r }));
324
- }
325
- const sizesAttr = sizes ?? DEFAULT_SIZES;
326
- const { webp, avif, jpeg, toSrcSet, fallback } = picture;
327
- const webpSet = toSrcSet(webp);
328
- const avifSet = toSrcSet(avif);
329
- const jpegSet = toSrcSet(jpeg);
330
- if (webpSet || avifSet || jpegSet) {
331
- return (_jsxs("picture", { children: [avifSet ? _jsx("source", { type: "image/avif", srcSet: avifSet, sizes: sizesAttr }) : null, webpSet ? _jsx("source", { type: "image/webp", srcSet: webpSet, sizes: sizesAttr }) : null, _jsx("img", { ref: imgRef, src: fallback, srcSet: jpegSet && !webpSet && !avifSet ? jpegSet : undefined, sizes: jpegSet && !webpSet && !avifSet ? sizesAttr : undefined, loading: loadingAttr, decoding: decodingAttr, alt: altAttr, title: titleAttr, width: widthAttr, height: heightAttr, "data-filename": dataFilename, ...rest })] }));
104
+ }, [
105
+ eagerLoad,
106
+ imgSrc,
107
+ isInView,
108
+ sizesAttr,
109
+ srcset.avif,
110
+ srcset.webp,
111
+ srcset.jpeg,
112
+ ]);
113
+ if (!hasSrcSet) {
114
+ return (_jsx("img", { ref: mergedRef, src: imgSrc, loading: loadingAttr, decoding: decodingAttr, alt: alt, title: title, width: widthAttr, height: heightAttr, ...restProps }));
332
115
  }
333
- return (_jsx("img", { ref: imgRef, src: fallback, loading: loadingAttr, decoding: decodingAttr, alt: altAttr, title: titleAttr, width: widthAttr, height: heightAttr, "data-filename": dataFilename, ...rest }));
116
+ return (_jsxs("picture", { ref: pictureRef, children: [srcset.avif ? (_jsx("source", { type: "image/avif", srcSet: srcset.avif, sizes: sizesAttr })) : null, srcset.webp ? (_jsx("source", { type: "image/webp", srcSet: srcset.webp, sizes: sizesAttr })) : null, _jsx("img", { ref: mergedRef, src: imgSrc, srcSet: inlineSrcSet || undefined, sizes: inlineSrcSet ? sizesAttr : undefined, loading: loadingAttr, decoding: decodingAttr, alt: alt, title: title, width: widthAttr, height: heightAttr, ...restProps })] }));
334
117
  };
335
118
  const ImgBase = forwardRef(function Img(props, ref) {
336
- const hasMediaId = Number.isFinite(props.mediaId);
337
- if (hasMediaId) {
338
- warnDeprecatedMediaId(props.mediaId);
339
- return _jsx(LegacyImg, { ...props, forwardedRef: ref });
340
- }
341
- const hasSrc = typeof props.src === 'string' && props.src.trim().length > 0;
119
+ const hasSrc = typeof props.src === "string" && props.src.trim().length > 0;
342
120
  if (!hasSrc) {
343
- if (typeof console !== 'undefined' && console.warn) {
344
- console.warn('<Img /> requires either src or mediaId. No src provided, rendering null.');
121
+ if (typeof console !== "undefined" && console.warn) {
122
+ console.warn("<Img /> requires src. No src provided, rendering null.");
345
123
  }
346
124
  return null;
347
125
  }
348
126
  return _jsx(ModernImg, { ...props, forwardedRef: ref });
349
127
  });
350
128
  export const Img = memo(ImgBase);
351
- Img.displayName = 'PageSpeedImg';
129
+ Img.displayName = "PageSpeedImg";
@@ -1,17 +1,11 @@
1
- import React from 'react';
2
- import type { OptixFlowConfig, ImageData } from '../types.js';
3
- type NativeImgProps = Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src' | 'srcSet' | 'sizes'> & {
1
+ import React from "react";
2
+ import type { UseOptimizedImageOptions } from "@page-speed/hooks/media";
3
+ type NativeImgProps = Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "srcSet" | "sizes"> & {
4
4
  src?: string;
5
5
  };
6
6
  export type ImgProps = NativeImgProps & {
7
- /** Legacy CDN media id support (deprecated) */
8
- mediaId?: number;
9
- /** Override CDN host for legacy mediaId usage */
10
- cdnHost?: string;
11
7
  /** Explicit sizes attribute (otherwise derived from useOptimizedImage) */
12
8
  sizes?: string;
13
- /** Callback when legacy mediaId payload is retrieved */
14
- onImageData?: (data: ImageData) => void;
15
9
  /** Force eager load (alias for loading="eager") */
16
10
  eager?: boolean;
17
11
  /** Intersection observer threshold for lazy loading */
@@ -19,20 +13,14 @@ export type ImgProps = NativeImgProps & {
19
13
  /** Intersection observer root margin for lazy loading */
20
14
  intersectionMargin?: string;
21
15
  /** OptixFlow integration options */
22
- optixFlowConfig?: OptixFlowConfig;
16
+ optixFlowConfig?: UseOptimizedImageOptions["optixFlowConfig"];
23
17
  };
24
- export declare const setDefaultOptixFlowConfig: (config?: OptixFlowConfig | null) => void;
18
+ export declare const setDefaultOptixFlowConfig: (config?: UseOptimizedImageOptions["optixFlowConfig"] | null) => void;
25
19
  export declare const Img: React.MemoExoticComponent<React.ForwardRefExoticComponent<Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "srcSet" | "sizes"> & {
26
20
  src?: string;
27
21
  } & {
28
- /** Legacy CDN media id support (deprecated) */
29
- mediaId?: number;
30
- /** Override CDN host for legacy mediaId usage */
31
- cdnHost?: string;
32
22
  /** Explicit sizes attribute (otherwise derived from useOptimizedImage) */
33
23
  sizes?: string;
34
- /** Callback when legacy mediaId payload is retrieved */
35
- onImageData?: (data: ImageData) => void;
36
24
  /** Force eager load (alias for loading="eager") */
37
25
  eager?: boolean;
38
26
  /** Intersection observer threshold for lazy loading */
@@ -40,6 +28,6 @@ export declare const Img: React.MemoExoticComponent<React.ForwardRefExoticCompon
40
28
  /** Intersection observer root margin for lazy loading */
41
29
  intersectionMargin?: string;
42
30
  /** OptixFlow integration options */
43
- optixFlowConfig?: OptixFlowConfig;
31
+ optixFlowConfig?: UseOptimizedImageOptions["optixFlowConfig"];
44
32
  } & React.RefAttributes<HTMLImageElement>>>;
45
33
  export {};