@wix/web5-core 1.63.20 → 1.63.21

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.
@@ -4,8 +4,10 @@ import { loadBackdropAnalysis } from '../../utils/imageBackdrop.js';
4
4
  import { isWeb5ImageUrl } from '../../types/link-types.js';
5
5
  import { useComponentDependencies } from '../../context/ComponentDependenciesContext.js';
6
6
  import { useResolvedImageSources } from '../../hooks/useResolvedImageSources.js';
7
+ import { backdropColorOf } from '../../image/imageSetTypes.js';
7
8
  const lowResFallbackCache = new Map();
8
9
  export const OptimizedImage = /*#__PURE__*/memo(props => {
10
+ var _slot$visualMetadata, _slot$crop;
9
11
  const {
10
12
  alt,
11
13
  className = '',
@@ -23,6 +25,7 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
23
25
  disableResize = false,
24
26
  imageMode = 'fit',
25
27
  adaptiveBackdrop = false,
28
+ slot,
26
29
  fallbackOnLowRes,
27
30
  lowResThreshold = 550
28
31
  } = props;
@@ -121,6 +124,21 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
121
124
  const effectiveObjectFit = adaptiveActive ? backdrop != null && backdrop.hasBackdrop ? 'contain' : 'cover' : objectFit;
122
125
  const effectiveImageMode = adaptiveActive ? backdrop != null && backdrop.hasBackdrop ? 'fit' : 'fill' : imageMode;
123
126
  const adaptiveBackgroundColor = adaptiveActive && backdrop != null && backdrop.hasBackdrop ? backdrop.backgroundColor : undefined;
127
+
128
+ // A resolved slot answers the same two questions the canvas probe was asking,
129
+ // from the server's own decode. It wins wherever it is present.
130
+ //
131
+ // `contained` (ADR 0220) forces `contain` regardless of anything else: the
132
+ // server deliberately returned a crop at a different aspect than the slot
133
+ // asked for, so that the whole product stays in frame. Cropping it again with
134
+ // `cover` would undo exactly the cut it went out of its way to avoid, and the
135
+ // padding it leaves behind is what `backgroundColor` is for — guaranteed
136
+ // non-empty in that case.
137
+ const slotBackground = slot == null || (_slot$visualMetadata = slot.visualMetadata) == null ? void 0 : _slot$visualMetadata.background;
138
+ const slotIsSolid = slotBackground === 'IMAGE_BACKGROUND_SOLID';
139
+ const slotWantsContain = Boolean(slot == null || (_slot$crop = slot.crop) == null ? void 0 : _slot$crop.contained) || slotIsSolid;
140
+ const resolvedObjectFit = slot ? slotWantsContain ? 'contain' : 'cover' : effectiveObjectFit;
141
+ const resolvedBackgroundColor = slot ? slotWantsContain ? backdropColorOf(slot) : undefined : adaptiveBackgroundColor;
124
142
  useEffect(() => {
125
143
  if (!adaptiveActive) {
126
144
  return;
@@ -339,7 +357,7 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
339
357
  className: ['block', layout === 'intrinsic' ? '' : 'relative overflow-hidden', showDynamicBadge ? 'relative' : '', className].filter(Boolean).join(' '),
340
358
  style: layout === 'intrinsic' ? undefined : {
341
359
  minHeight: '1px',
342
- backgroundColor: adaptiveBackgroundColor
360
+ backgroundColor: resolvedBackgroundColor
343
361
  },
344
362
  onClick: clickable ? onClick : undefined,
345
363
  onKeyDown: clickable ? handleKeyDown : undefined,
@@ -376,7 +394,7 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
376
394
  }, "Failed to load image"))), imageUrl && !isError && /*#__PURE__*/React.createElement("img", {
377
395
  src: imageUrl,
378
396
  alt: alt,
379
- className: `${layout === 'intrinsic' ? 'w-full h-auto' : 'absolute inset-0 w-full h-full'} transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'} ${effectiveObjectFit === 'contain' ? 'object-contain' : effectiveObjectFit === 'cover' ? 'object-cover' : effectiveObjectFit === 'fill' ? 'object-fill' : effectiveObjectFit === 'none' ? 'object-none' : effectiveObjectFit === 'scale-down' ? 'object-scale-down' : ''}`,
397
+ className: `${layout === 'intrinsic' ? 'w-full h-auto' : 'absolute inset-0 w-full h-full'} transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'} ${resolvedObjectFit === 'contain' ? 'object-contain' : resolvedObjectFit === 'cover' ? 'object-cover' : resolvedObjectFit === 'fill' ? 'object-fill' : resolvedObjectFit === 'none' ? 'object-none' : resolvedObjectFit === 'scale-down' ? 'object-scale-down' : ''}`,
380
398
  style: layout === 'intrinsic' ? {
381
399
  width: '100%',
382
400
  height: 'auto',
@@ -387,7 +405,7 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
387
405
  inset: 0,
388
406
  width: '100%',
389
407
  height: '100%',
390
- objectFit: effectiveObjectFit,
408
+ objectFit: resolvedObjectFit,
391
409
  maxWidth: '100%',
392
410
  display: 'block'
393
411
  },
@@ -1 +1 @@
1
- {"version":3,"names":["React","useState","useRef","useEffect","memo","useMemo","getResizedImageUrl","normalizeImageUrl","loadBackdropAnalysis","isWeb5ImageUrl","useComponentDependencies","useResolvedImageSources","lowResFallbackCache","Map","OptimizedImage","props","alt","className","quality","format","progressive","placeholderColor","objectFit","layout","onLoad","onError","onClick","clickable","imageFromText","disableResize","imageMode","adaptiveBackdrop","fallbackOnLowRes","lowResThreshold","isResolvedMode","unresolvedSource","filename","preResolvedUrl","resolvedUrl","filter","undefined","sourceIdentifier","deps","isLoaded","setIsLoaded","isError","setIsError","imageUrl","setImageUrl","isFallback","setIsFallback","backdrop","setBackdrop","isDebugImageEnabled","useDebugImageContext","dimensions","setDimensions","containerRef","observerRef","isContextualImage","contextualImageKey","rawKey","slice","length","decodeURIComponent","trim","resolutionInputs","src","images","resolvedSources","loading","isResolutionLoading","resolvedSource","logContextualImage","url","contextualTerm","imageEventType","isImageResolved","isFallbackImage","contextualImageFallback","fallbacks","fallbackImages","Math","floor","random","effectiveFilename","sourceCacheKey","hasResolvedSourceDimensions","width","height","isLowResResolvedAsset","cachedFallbackUrl","get","showDynamicBadge","adaptiveActive","adaptiveResolved","effectiveObjectFit","hasBackdrop","effectiveImageMode","adaptiveBackgroundColor","backgroundColor","token","aborted","then","result","set","container","current","updateDimensions","rect","getBoundingClientRect","ceil","window","ResizeObserver","observe","handleResize","addEventListener","disconnect","removeEventListener","normalizedUrl","isExternalNonWixUrl","startsWith","includes","imageFilename","match","replace","dpr","devicePixelRatio","optimizedUrl","handleImageLoad","e","img","currentTarget","naturalWidth","naturalHeight","originalImageUrl","currentSrc","console","warn","fallbackImageUrl","handleImageError","error","source","Error","isTransparentPlaceholder","placeholderStyles","background","animation","backgroundSize","handleKeyDown","key","preventDefault","createElement","Fragment","ref","Boolean","join","style","minHeight","onKeyDown","tabIndex","role","fill","stroke","viewBox","focusable","strokeLinecap","strokeLinejoin","strokeWidth","d","maxWidth","display","position","inset"],"sources":["../../../../src/components/ui/OptimizedImage.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, memo, useMemo } from 'react';\nimport { getResizedImageUrl, normalizeImageUrl } from '../../utils/image-utils';\nimport {\n loadBackdropAnalysis,\n type BackdropAnalysis,\n} from '../../utils/imageBackdrop';\nimport { isWeb5ImageUrl } from '../../types/link-types';\nimport { useComponentDependencies } from '../../context/ComponentDependenciesContext';\nimport type { ImageSearchFilterString } from '../../image/imageSearchFilterTypes';\nimport { useResolvedImageSources } from '../../hooks/useResolvedImageSources';\n\nconst lowResFallbackCache = new Map<string, string>();\n\ninterface OptimizedImageBaseProps {\n /** Accessible alternative text for the rendered image. */\n alt: string;\n /** Additional CSS classes applied to the outer image container. */\n className?: string;\n /** Preferred output quality for resized Wix media URLs. */\n quality?: number;\n /** Requested output format for future image delivery customization. */\n format?: 'auto' | 'webp' | 'jpg' | 'png';\n /** Enables progressive loading behavior when supported by the delivery layer. */\n progressive?: boolean;\n /** Placeholder background color shown before the image loads. */\n placeholderColor?: string;\n /** CSS object-fit behavior applied to the rendered `<img>`. */\n objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n /** Layout strategy for the image container. */\n layout?: 'fill' | 'intrinsic';\n /** Called after the final rendered image successfully loads. */\n onLoad?: () => void;\n /** Called when the final rendered image fails to load. */\n onError?: (error: Error) => void;\n /** Click handler for interactive image surfaces. */\n onClick?: () => void;\n /** Makes the image container keyboard-focusable and clickable. */\n clickable?: boolean;\n /** Marks the source as originating from text-driven image generation for BI logging. */\n imageFromText?: boolean;\n /** Skips Wix fitted delivery URL rewriting and uses the normalized original URL directly. */\n disableResize?: boolean;\n /** CDN resize mode. 'fill' crops to exact container dimensions (sharp at slot size).\n * 'fit' (default) preserves aspect ratio. */\n imageMode?: 'fit' | 'fill';\n /**\n * Auto-pick `objectFit`/`imageMode` per image by sniffing its edges on a\n * canvas (see `imageBackdrop.ts`). Images shot on a seamless backdrop are\n * rendered `contain` with the slot painted that backdrop colour (the subject\n * is never cropped, the letterbox is invisible); full-bleed scenes fall back\n * to `cover`. Overrides `objectFit`/`imageMode` once analysis resolves; until\n * then, and on any probe/CORS failure, the passed `objectFit`/`imageMode`\n * apply. No effect for `layout=\"intrinsic\"`. */\n adaptiveBackdrop?: boolean;\n /** Full URL to a fallback image used when the primary image is considered low resolution. */\n fallbackOnLowRes?: string;\n /** Minimum acceptable width or height before low-resolution fallback is triggered. */\n lowResThreshold?: number;\n}\n\ninterface OptimizedImageWithFilenameProps {\n /** Image source to render. Supports full URLs and `web5://image/...` contextual image tokens. */\n filename: string;\n /** Optional contextual search filter used only when resolving a `web5://image/...` source. */\n filter?: ImageSearchFilterString;\n /** Mutually exclusive with `filename`; provide this only when the image URL is already resolved. */\n resolvedUrl?: never;\n}\n\ninterface OptimizedImageWithResolvedUrlProps {\n /** Already-resolved image URL. When provided, OptimizedImage skips all source resolution. */\n resolvedUrl: string;\n /** Mutually exclusive with `resolvedUrl`; use `filename` when the source still needs resolving. */\n filename?: never;\n /** Not applicable for already-resolved image URLs. */\n filter?: never;\n}\n\nexport type OptimizedImageProps = OptimizedImageBaseProps &\n (OptimizedImageWithFilenameProps | OptimizedImageWithResolvedUrlProps);\n\nexport const OptimizedImage: React.FC<OptimizedImageProps> = memo((props) => {\n const {\n alt,\n className = '',\n quality = 85,\n format = 'auto',\n progressive = true,\n placeholderColor = '#f3f4f6',\n objectFit = 'contain',\n layout = 'fill',\n onLoad,\n onError,\n onClick,\n clickable = false,\n imageFromText = false,\n disableResize = false,\n imageMode = 'fit',\n adaptiveBackdrop = false,\n fallbackOnLowRes,\n lowResThreshold = 550,\n } = props;\n void format;\n void progressive;\n\n const isResolvedMode = 'resolvedUrl' in props;\n const unresolvedSource = 'filename' in props ? props.filename ?? '' : '';\n const preResolvedUrl = 'resolvedUrl' in props ? props.resolvedUrl : '';\n const filter = 'filename' in props ? props.filter : undefined;\n const sourceIdentifier = isResolvedMode ? preResolvedUrl : unresolvedSource;\n\n const deps = useComponentDependencies();\n const [isLoaded, setIsLoaded] = useState(false);\n const [isError, setIsError] = useState(false);\n const [imageUrl, setImageUrl] = useState<string>('');\n const [isFallback, setIsFallback] = useState(false);\n // null = adaptive analysis pending (only meaningful when adaptiveBackdrop).\n const [backdrop, setBackdrop] = useState<BackdropAnalysis | null>(null);\n const isDebugImageEnabled = deps.useDebugImageContext();\n const [dimensions, setDimensions] = useState<{\n width: number;\n height: number;\n } | null>(null);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const observerRef = useRef<ResizeObserver | null>(null);\n\n const isContextualImage = useMemo(\n () =>\n !isResolvedMode &&\n !!(unresolvedSource && isWeb5ImageUrl(unresolvedSource)),\n [isResolvedMode, unresolvedSource],\n );\n\n const contextualImageKey = useMemo(() => {\n if (!isContextualImage) {\n return '';\n }\n const rawKey = unresolvedSource.slice('web5://image/'.length);\n try {\n return decodeURIComponent(rawKey).trim();\n } catch {\n return rawKey.trim();\n }\n }, [isContextualImage, unresolvedSource]);\n const resolutionInputs = useMemo(\n () =>\n isResolvedMode || !unresolvedSource\n ? []\n : [\n {\n src: unresolvedSource,\n filter,\n },\n ],\n [filter, isResolvedMode, unresolvedSource],\n );\n const { images: resolvedSources, loading: isResolutionLoading } =\n useResolvedImageSources(resolutionInputs);\n const resolvedSource = useMemo(\n () => (!isResolvedMode ? resolvedSources?.[0] ?? null : null),\n [isResolvedMode, resolvedSources],\n );\n\n useEffect(() => {\n if (\n !isContextualImage ||\n isResolvedMode ||\n isResolutionLoading ||\n resolvedSources === null\n ) {\n return;\n }\n\n deps.logContextualImage?.({\n imageUrl: resolvedSource?.url ?? '',\n contextualTerm: contextualImageKey,\n imageEventType: imageFromText\n ? 'web5_image_link_from_text'\n : 'web5_image_link',\n isImageResolved: !!resolvedSource?.url,\n isFallbackImage: false,\n });\n }, [\n contextualImageKey,\n deps,\n imageFromText,\n isContextualImage,\n isResolvedMode,\n isResolutionLoading,\n resolvedSource?.url,\n resolvedSources,\n ]);\n\n // When imageFromText contextual image resolution returns empty, use a random fallback\n const contextualImageFallback = useMemo(() => {\n if (\n !imageFromText ||\n !isContextualImage ||\n isResolutionLoading ||\n resolvedSources === null ||\n resolvedSource?.url\n ) {\n return '';\n }\n const fallbacks = deps.fallbackImages;\n if (!fallbacks || fallbacks.length === 0) {\n return '';\n }\n return fallbacks[Math.floor(Math.random() * fallbacks.length)];\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n imageFromText,\n isContextualImage,\n isResolutionLoading,\n resolvedSources,\n resolvedSource?.url,\n deps.fallbackImages,\n ]);\n\n // Log when a fallback image is used for an unresolved contextual image\n useEffect(() => {\n if (!contextualImageFallback) {\n return;\n }\n deps.logContextualImage?.({\n imageUrl: contextualImageFallback,\n contextualTerm: contextualImageKey,\n imageEventType: 'web5_image_fallback',\n isImageResolved: false,\n isFallbackImage: true,\n });\n }, [contextualImageFallback, contextualImageKey, deps]);\n\n const effectiveFilename = isResolvedMode\n ? preResolvedUrl\n : resolvedSource?.url || contextualImageFallback;\n const sourceCacheKey = useMemo(\n () =>\n effectiveFilename ? normalizeImageUrl(effectiveFilename).trim() : '',\n [effectiveFilename],\n );\n const hasResolvedSourceDimensions =\n typeof resolvedSource?.width === 'number' &&\n typeof resolvedSource?.height === 'number';\n const isLowResResolvedAsset =\n hasResolvedSourceDimensions &&\n (resolvedSource.width! < lowResThreshold ||\n resolvedSource.height! < lowResThreshold);\n const cachedFallbackUrl = useMemo(\n () => (sourceCacheKey ? lowResFallbackCache.get(sourceCacheKey) ?? '' : ''),\n [sourceCacheKey],\n );\n const showDynamicBadge = isContextualImage && isDebugImageEnabled;\n\n // Adaptive fit: probe the resolved image's edges, then map to fit/object-fit.\n // Until analysis resolves (`backdrop === null`) the displayed URL is held back\n // so we never fetch with the wrong mode and flash a re-crop. On a backdrop we\n // render `contain` (whole subject visible) and paint the slot its colour; on a\n // busy scene we `cover`. Failures resolve to `hasBackdrop: false` → `cover`.\n const adaptiveActive = adaptiveBackdrop && layout !== 'intrinsic';\n const adaptiveResolved = !adaptiveActive || backdrop !== null;\n const effectiveObjectFit = adaptiveActive\n ? backdrop?.hasBackdrop\n ? 'contain'\n : 'cover'\n : objectFit;\n const effectiveImageMode = adaptiveActive\n ? backdrop?.hasBackdrop\n ? 'fit'\n : 'fill'\n : imageMode;\n const adaptiveBackgroundColor =\n adaptiveActive && backdrop?.hasBackdrop\n ? backdrop.backgroundColor\n : undefined;\n\n useEffect(() => {\n if (!adaptiveActive) {\n return;\n }\n setBackdrop(null);\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n // No analyzable primary source → behave like a busy scene (cover).\n setBackdrop({ hasBackdrop: false });\n return;\n }\n const token = { aborted: false };\n loadBackdropAnalysis(effectiveFilename, token).then((result) => {\n if (!token.aborted) {\n setBackdrop(result);\n }\n });\n return () => {\n token.aborted = true;\n };\n }, [\n adaptiveActive,\n effectiveFilename,\n isFallback,\n cachedFallbackUrl,\n isLowResResolvedAsset,\n fallbackOnLowRes,\n ]);\n\n useEffect(() => {\n setIsLoaded(false);\n setIsError(false);\n setIsFallback(false);\n setImageUrl('');\n }, [sourceCacheKey, fallbackOnLowRes]);\n\n useEffect(() => {\n if (!sourceCacheKey || !fallbackOnLowRes) {\n return;\n }\n\n if (isLowResResolvedAsset) {\n lowResFallbackCache.set(sourceCacheKey, fallbackOnLowRes);\n setIsFallback(true);\n setImageUrl(fallbackOnLowRes);\n return;\n }\n\n if (!cachedFallbackUrl) {\n return;\n }\n\n setIsFallback(true);\n setImageUrl(cachedFallbackUrl);\n }, [\n cachedFallbackUrl,\n fallbackOnLowRes,\n isLowResResolvedAsset,\n sourceCacheKey,\n ]);\n\n // For fill layout, measure container so we can request a resized Wix image.\n useEffect(() => {\n if (layout === 'intrinsic' || disableResize) {\n return;\n }\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n const updateDimensions = () => {\n const rect = container.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n setDimensions({\n width: Math.ceil(rect.width),\n height: Math.ceil(rect.height),\n });\n }\n };\n\n updateDimensions();\n\n if ('ResizeObserver' in window) {\n observerRef.current = new ResizeObserver(() => {\n updateDimensions();\n });\n observerRef.current.observe(container);\n }\n\n const handleResize = () => updateDimensions();\n window.addEventListener('resize', handleResize);\n\n return () => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n window.removeEventListener('resize', handleResize);\n };\n }, [disableResize, layout]);\n\n // Intrinsic layout: use normalized URL without resize logic.\n useEffect(() => {\n if (layout !== 'intrinsic') {\n return;\n }\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n setImageUrl(normalizedUrl);\n }, [\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // External non-Wix URLs skip resizing; use them immediately.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n const isExternalNonWixUrl =\n (normalizedUrl.startsWith('http://') ||\n normalizedUrl.startsWith('https://')) &&\n !normalizedUrl.includes('static.wixstatic.com');\n\n if (isExternalNonWixUrl) {\n setImageUrl(normalizedUrl);\n }\n }, [\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // Explicit opt-out: use original normalized URL even in fill layouts.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (\n !disableResize ||\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n setImageUrl(normalizeImageUrl(effectiveFilename));\n }, [\n cachedFallbackUrl,\n disableResize,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // Wix/local URLs: wait for dimensions, then build a resized URL.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (disableResize) {\n return;\n }\n // Hold the fetch until adaptive analysis picks fit vs fill, so we request\n // the right CDN mode once instead of fetching twice and re-cropping.\n if (!adaptiveResolved) {\n return;\n }\n if (\n !dimensions ||\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n const isExternalNonWixUrl =\n (normalizedUrl.startsWith('http://') ||\n normalizedUrl.startsWith('https://')) &&\n !normalizedUrl.includes('static.wixstatic.com');\n\n if (isExternalNonWixUrl) {\n return;\n }\n\n let imageFilename = effectiveFilename;\n if (normalizedUrl.includes('static.wixstatic.com')) {\n const match = normalizedUrl.match(/media\\/([^/?]+)/);\n if (match && match[1]) {\n imageFilename = match[1];\n } else {\n setImageUrl(normalizedUrl);\n return;\n }\n } else if (\n normalizedUrl.startsWith('https://static.wixstatic.com/media/')\n ) {\n imageFilename = normalizedUrl.replace(\n 'https://static.wixstatic.com/media/',\n '',\n );\n }\n\n const dpr = window.devicePixelRatio ?? 1;\n const optimizedUrl = getResizedImageUrl(\n imageFilename,\n dimensions.width * dpr,\n dimensions.height * dpr,\n { quality, imageMode: effectiveImageMode },\n );\n setImageUrl(optimizedUrl);\n }, [\n dimensions,\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n disableResize,\n quality,\n effectiveImageMode,\n adaptiveResolved,\n ]);\n\n const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const img = e.currentTarget;\n\n if (\n fallbackOnLowRes &&\n !isFallback &&\n !hasResolvedSourceDimensions &&\n (img.naturalWidth < lowResThreshold ||\n img.naturalHeight < lowResThreshold)\n ) {\n const originalImageUrl =\n img.currentSrc || imageUrl || effectiveFilename || sourceIdentifier;\n\n if (sourceCacheKey) {\n lowResFallbackCache.set(sourceCacheKey, fallbackOnLowRes);\n }\n\n setIsFallback(true);\n setImageUrl(fallbackOnLowRes);\n setIsLoaded(false);\n\n console.warn('OptimizedImage applied a low-res fallback image.', {\n originalImageUrl,\n fallbackImageUrl: fallbackOnLowRes,\n naturalWidth: img.naturalWidth,\n naturalHeight: img.naturalHeight,\n lowResThreshold,\n });\n\n deps.logContextualImage?.({\n imageUrl: fallbackOnLowRes,\n contextualTerm: contextualImageKey,\n imageEventType: 'web5_image_fallback',\n isImageResolved: true,\n isFallbackImage: true,\n });\n return;\n }\n\n setIsLoaded(true);\n onLoad?.();\n };\n\n const handleImageError = () => {\n setIsError(true);\n console.error('Failed to load image:', {\n source: sourceIdentifier,\n imageUrl,\n alt,\n });\n onError?.(new Error(`Failed to load image: ${sourceIdentifier}`));\n };\n\n const isTransparentPlaceholder = placeholderColor === 'transparent';\n const placeholderStyles = isTransparentPlaceholder\n ? {\n background: 'transparent',\n animation: 'none',\n }\n : {\n background: `linear-gradient(90deg, ${placeholderColor} 0%, #e5e7eb 50%, ${placeholderColor} 100%)`,\n backgroundSize: '200% 100%',\n animation: 'shimmer 2s infinite ease-in-out',\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick();\n }\n };\n\n return (\n <>\n <span\n ref={containerRef}\n className={[\n 'block',\n layout === 'intrinsic' ? '' : 'relative overflow-hidden',\n showDynamicBadge ? 'relative' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n style={\n layout === 'intrinsic'\n ? undefined\n : { minHeight: '1px', backgroundColor: adaptiveBackgroundColor }\n }\n onClick={clickable ? onClick : undefined}\n onKeyDown={clickable ? handleKeyDown : undefined}\n tabIndex={clickable ? 0 : undefined}\n role={clickable ? 'button' : undefined}\n aria-label={clickable ? alt : undefined}\n >\n {showDynamicBadge && (\n <span className=\"absolute left-2 top-2 z-10 rounded bg-blue-600/90 px-2 py-1 text-[10px] font-semibold tracking-wide text-white shadow block\">\n {contextualImageKey}\n </span>\n )}\n {!isLoaded && !isError && sourceIdentifier && (\n <span\n className=\"absolute inset-0 rounded block\"\n style={placeholderStyles}\n role=\"status\"\n aria-label=\"Loading image\"\n />\n )}\n\n {isError && (\n <span\n className=\"absolute inset-0 flex items-center justify-center bg-gray-100 text-gray-500 rounded block\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n <span className=\"text-center p-4 block\">\n <svg\n className=\"w-8 h-8 mx-auto mb-2 text-gray-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n <span className=\"text-xs block\">Failed to load image</span>\n </span>\n </span>\n )}\n\n {imageUrl && !isError && (\n <img\n src={imageUrl}\n alt={alt}\n className={`${\n layout === 'intrinsic'\n ? 'w-full h-auto'\n : 'absolute inset-0 w-full h-full'\n } transition-opacity duration-300 ${\n isLoaded ? 'opacity-100' : 'opacity-0'\n } ${\n effectiveObjectFit === 'contain'\n ? 'object-contain'\n : effectiveObjectFit === 'cover'\n ? 'object-cover'\n : effectiveObjectFit === 'fill'\n ? 'object-fill'\n : effectiveObjectFit === 'none'\n ? 'object-none'\n : effectiveObjectFit === 'scale-down'\n ? 'object-scale-down'\n : ''\n }`}\n style={\n layout === 'intrinsic'\n ? {\n width: '100%',\n height: 'auto',\n maxWidth: '100%',\n display: 'block',\n }\n : {\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: effectiveObjectFit,\n maxWidth: '100%',\n display: 'block',\n }\n }\n onLoad={handleImageLoad}\n onError={handleImageError}\n loading={imageUrl.startsWith('data:') ? 'eager' : 'lazy'}\n />\n )}\n </span>\n </>\n );\n});\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["React","useState","useRef","useEffect","memo","useMemo","getResizedImageUrl","normalizeImageUrl","loadBackdropAnalysis","isWeb5ImageUrl","useComponentDependencies","useResolvedImageSources","backdropColorOf","lowResFallbackCache","Map","OptimizedImage","props","_slot$visualMetadata","_slot$crop","alt","className","quality","format","progressive","placeholderColor","objectFit","layout","onLoad","onError","onClick","clickable","imageFromText","disableResize","imageMode","adaptiveBackdrop","slot","fallbackOnLowRes","lowResThreshold","isResolvedMode","unresolvedSource","filename","preResolvedUrl","resolvedUrl","filter","undefined","sourceIdentifier","deps","isLoaded","setIsLoaded","isError","setIsError","imageUrl","setImageUrl","isFallback","setIsFallback","backdrop","setBackdrop","isDebugImageEnabled","useDebugImageContext","dimensions","setDimensions","containerRef","observerRef","isContextualImage","contextualImageKey","rawKey","slice","length","decodeURIComponent","trim","resolutionInputs","src","images","resolvedSources","loading","isResolutionLoading","resolvedSource","logContextualImage","url","contextualTerm","imageEventType","isImageResolved","isFallbackImage","contextualImageFallback","fallbacks","fallbackImages","Math","floor","random","effectiveFilename","sourceCacheKey","hasResolvedSourceDimensions","width","height","isLowResResolvedAsset","cachedFallbackUrl","get","showDynamicBadge","adaptiveActive","adaptiveResolved","effectiveObjectFit","hasBackdrop","effectiveImageMode","adaptiveBackgroundColor","backgroundColor","slotBackground","visualMetadata","background","slotIsSolid","slotWantsContain","Boolean","crop","contained","resolvedObjectFit","resolvedBackgroundColor","token","aborted","then","result","set","container","current","updateDimensions","rect","getBoundingClientRect","ceil","window","ResizeObserver","observe","handleResize","addEventListener","disconnect","removeEventListener","normalizedUrl","isExternalNonWixUrl","startsWith","includes","imageFilename","match","replace","dpr","devicePixelRatio","optimizedUrl","handleImageLoad","e","img","currentTarget","naturalWidth","naturalHeight","originalImageUrl","currentSrc","console","warn","fallbackImageUrl","handleImageError","error","source","Error","isTransparentPlaceholder","placeholderStyles","animation","backgroundSize","handleKeyDown","key","preventDefault","createElement","Fragment","ref","join","style","minHeight","onKeyDown","tabIndex","role","fill","stroke","viewBox","focusable","strokeLinecap","strokeLinejoin","strokeWidth","d","maxWidth","display","position","inset"],"sources":["../../../../src/components/ui/OptimizedImage.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, memo, useMemo } from 'react';\nimport { getResizedImageUrl, normalizeImageUrl } from '../../utils/image-utils';\nimport {\n loadBackdropAnalysis,\n type BackdropAnalysis,\n} from '../../utils/imageBackdrop';\nimport { isWeb5ImageUrl } from '../../types/link-types';\nimport { useComponentDependencies } from '../../context/ComponentDependenciesContext';\nimport type { ImageSearchFilterString } from '../../image/imageSearchFilterTypes';\nimport { useResolvedImageSources } from '../../hooks/useResolvedImageSources';\nimport {\n backdropColorOf,\n type ResolvedImageSlot,\n} from '../../image/imageSetTypes';\n\nconst lowResFallbackCache = new Map<string, string>();\n\ninterface OptimizedImageBaseProps {\n /** Accessible alternative text for the rendered image. */\n alt: string;\n /** Additional CSS classes applied to the outer image container. */\n className?: string;\n /** Preferred output quality for resized Wix media URLs. */\n quality?: number;\n /** Requested output format for future image delivery customization. */\n format?: 'auto' | 'webp' | 'jpg' | 'png';\n /** Enables progressive loading behavior when supported by the delivery layer. */\n progressive?: boolean;\n /** Placeholder background color shown before the image loads. */\n placeholderColor?: string;\n /** CSS object-fit behavior applied to the rendered `<img>`. */\n objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n /** Layout strategy for the image container. */\n layout?: 'fill' | 'intrinsic';\n /** Called after the final rendered image successfully loads. */\n onLoad?: () => void;\n /** Called when the final rendered image fails to load. */\n onError?: (error: Error) => void;\n /** Click handler for interactive image surfaces. */\n onClick?: () => void;\n /** Makes the image container keyboard-focusable and clickable. */\n clickable?: boolean;\n /** Marks the source as originating from text-driven image generation for BI logging. */\n imageFromText?: boolean;\n /** Skips Wix fitted delivery URL rewriting and uses the normalized original URL directly. */\n disableResize?: boolean;\n /** CDN resize mode. 'fill' crops to exact container dimensions (sharp at slot size).\n * 'fit' (default) preserves aspect ratio. */\n imageMode?: 'fit' | 'fill';\n /**\n * Auto-pick `objectFit`/`imageMode` per image by sniffing its edges on a\n * canvas (see `imageBackdrop.ts`). Images shot on a seamless backdrop are\n * rendered `contain` with the slot painted that backdrop colour (the subject\n * is never cropped, the letterbox is invisible); full-bleed scenes fall back\n * to `cover`. Overrides `objectFit`/`imageMode` once analysis resolves; until\n * then, and on any probe/CORS failure, the passed `objectFit`/`imageMode`\n * apply. No effect for `layout=\"intrinsic\"`. */\n adaptiveBackdrop?: boolean;\n /**\n * A slot resolved by `ResolveImageSet` (ADR 0221). Supplying it makes the\n * server's own decode authoritative over `adaptiveBackdrop`'s canvas probe:\n * fit comes from `visualMetadata.background` and the backdrop colour from\n * `backgroundColor` (falling back to the dominant swatch for a scene), so no\n * pixel is read back, no CORS is needed and nothing is guessed.\n *\n * Pair it with `resolvedUrl={slot.imageUrl}` and `disableResize` — the URL\n * already carries the crop and the render size.\n */\n slot?: ResolvedImageSlot;\n /** Full URL to a fallback image used when the primary image is considered low resolution. */\n fallbackOnLowRes?: string;\n /** Minimum acceptable width or height before low-resolution fallback is triggered. */\n lowResThreshold?: number;\n}\n\ninterface OptimizedImageWithFilenameProps {\n /** Image source to render. Supports full URLs and `web5://image/...` contextual image tokens. */\n filename: string;\n /** Optional contextual search filter used only when resolving a `web5://image/...` source. */\n filter?: ImageSearchFilterString;\n /** Mutually exclusive with `filename`; provide this only when the image URL is already resolved. */\n resolvedUrl?: never;\n}\n\ninterface OptimizedImageWithResolvedUrlProps {\n /** Already-resolved image URL. When provided, OptimizedImage skips all source resolution. */\n resolvedUrl: string;\n /** Mutually exclusive with `resolvedUrl`; use `filename` when the source still needs resolving. */\n filename?: never;\n /** Not applicable for already-resolved image URLs. */\n filter?: never;\n}\n\nexport type OptimizedImageProps = OptimizedImageBaseProps &\n (OptimizedImageWithFilenameProps | OptimizedImageWithResolvedUrlProps);\n\nexport const OptimizedImage: React.FC<OptimizedImageProps> = memo((props) => {\n const {\n alt,\n className = '',\n quality = 85,\n format = 'auto',\n progressive = true,\n placeholderColor = '#f3f4f6',\n objectFit = 'contain',\n layout = 'fill',\n onLoad,\n onError,\n onClick,\n clickable = false,\n imageFromText = false,\n disableResize = false,\n imageMode = 'fit',\n adaptiveBackdrop = false,\n slot,\n fallbackOnLowRes,\n lowResThreshold = 550,\n } = props;\n void format;\n void progressive;\n\n const isResolvedMode = 'resolvedUrl' in props;\n const unresolvedSource = 'filename' in props ? props.filename ?? '' : '';\n const preResolvedUrl = 'resolvedUrl' in props ? props.resolvedUrl : '';\n const filter = 'filename' in props ? props.filter : undefined;\n const sourceIdentifier = isResolvedMode ? preResolvedUrl : unresolvedSource;\n\n const deps = useComponentDependencies();\n const [isLoaded, setIsLoaded] = useState(false);\n const [isError, setIsError] = useState(false);\n const [imageUrl, setImageUrl] = useState<string>('');\n const [isFallback, setIsFallback] = useState(false);\n // null = adaptive analysis pending (only meaningful when adaptiveBackdrop).\n const [backdrop, setBackdrop] = useState<BackdropAnalysis | null>(null);\n const isDebugImageEnabled = deps.useDebugImageContext();\n const [dimensions, setDimensions] = useState<{\n width: number;\n height: number;\n } | null>(null);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const observerRef = useRef<ResizeObserver | null>(null);\n\n const isContextualImage = useMemo(\n () =>\n !isResolvedMode &&\n !!(unresolvedSource && isWeb5ImageUrl(unresolvedSource)),\n [isResolvedMode, unresolvedSource],\n );\n\n const contextualImageKey = useMemo(() => {\n if (!isContextualImage) {\n return '';\n }\n const rawKey = unresolvedSource.slice('web5://image/'.length);\n try {\n return decodeURIComponent(rawKey).trim();\n } catch {\n return rawKey.trim();\n }\n }, [isContextualImage, unresolvedSource]);\n const resolutionInputs = useMemo(\n () =>\n isResolvedMode || !unresolvedSource\n ? []\n : [\n {\n src: unresolvedSource,\n filter,\n },\n ],\n [filter, isResolvedMode, unresolvedSource],\n );\n const { images: resolvedSources, loading: isResolutionLoading } =\n useResolvedImageSources(resolutionInputs);\n const resolvedSource = useMemo(\n () => (!isResolvedMode ? resolvedSources?.[0] ?? null : null),\n [isResolvedMode, resolvedSources],\n );\n\n useEffect(() => {\n if (\n !isContextualImage ||\n isResolvedMode ||\n isResolutionLoading ||\n resolvedSources === null\n ) {\n return;\n }\n\n deps.logContextualImage?.({\n imageUrl: resolvedSource?.url ?? '',\n contextualTerm: contextualImageKey,\n imageEventType: imageFromText\n ? 'web5_image_link_from_text'\n : 'web5_image_link',\n isImageResolved: !!resolvedSource?.url,\n isFallbackImage: false,\n });\n }, [\n contextualImageKey,\n deps,\n imageFromText,\n isContextualImage,\n isResolvedMode,\n isResolutionLoading,\n resolvedSource?.url,\n resolvedSources,\n ]);\n\n // When imageFromText contextual image resolution returns empty, use a random fallback\n const contextualImageFallback = useMemo(() => {\n if (\n !imageFromText ||\n !isContextualImage ||\n isResolutionLoading ||\n resolvedSources === null ||\n resolvedSource?.url\n ) {\n return '';\n }\n const fallbacks = deps.fallbackImages;\n if (!fallbacks || fallbacks.length === 0) {\n return '';\n }\n return fallbacks[Math.floor(Math.random() * fallbacks.length)];\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n imageFromText,\n isContextualImage,\n isResolutionLoading,\n resolvedSources,\n resolvedSource?.url,\n deps.fallbackImages,\n ]);\n\n // Log when a fallback image is used for an unresolved contextual image\n useEffect(() => {\n if (!contextualImageFallback) {\n return;\n }\n deps.logContextualImage?.({\n imageUrl: contextualImageFallback,\n contextualTerm: contextualImageKey,\n imageEventType: 'web5_image_fallback',\n isImageResolved: false,\n isFallbackImage: true,\n });\n }, [contextualImageFallback, contextualImageKey, deps]);\n\n const effectiveFilename = isResolvedMode\n ? preResolvedUrl\n : resolvedSource?.url || contextualImageFallback;\n const sourceCacheKey = useMemo(\n () =>\n effectiveFilename ? normalizeImageUrl(effectiveFilename).trim() : '',\n [effectiveFilename],\n );\n const hasResolvedSourceDimensions =\n typeof resolvedSource?.width === 'number' &&\n typeof resolvedSource?.height === 'number';\n const isLowResResolvedAsset =\n hasResolvedSourceDimensions &&\n (resolvedSource.width! < lowResThreshold ||\n resolvedSource.height! < lowResThreshold);\n const cachedFallbackUrl = useMemo(\n () => (sourceCacheKey ? lowResFallbackCache.get(sourceCacheKey) ?? '' : ''),\n [sourceCacheKey],\n );\n const showDynamicBadge = isContextualImage && isDebugImageEnabled;\n\n // Adaptive fit: probe the resolved image's edges, then map to fit/object-fit.\n // Until analysis resolves (`backdrop === null`) the displayed URL is held back\n // so we never fetch with the wrong mode and flash a re-crop. On a backdrop we\n // render `contain` (whole subject visible) and paint the slot its colour; on a\n // busy scene we `cover`. Failures resolve to `hasBackdrop: false` → `cover`.\n const adaptiveActive = adaptiveBackdrop && layout !== 'intrinsic';\n const adaptiveResolved = !adaptiveActive || backdrop !== null;\n const effectiveObjectFit = adaptiveActive\n ? backdrop?.hasBackdrop\n ? 'contain'\n : 'cover'\n : objectFit;\n const effectiveImageMode = adaptiveActive\n ? backdrop?.hasBackdrop\n ? 'fit'\n : 'fill'\n : imageMode;\n const adaptiveBackgroundColor =\n adaptiveActive && backdrop?.hasBackdrop\n ? backdrop.backgroundColor\n : undefined;\n\n // A resolved slot answers the same two questions the canvas probe was asking,\n // from the server's own decode. It wins wherever it is present.\n //\n // `contained` (ADR 0220) forces `contain` regardless of anything else: the\n // server deliberately returned a crop at a different aspect than the slot\n // asked for, so that the whole product stays in frame. Cropping it again with\n // `cover` would undo exactly the cut it went out of its way to avoid, and the\n // padding it leaves behind is what `backgroundColor` is for — guaranteed\n // non-empty in that case.\n const slotBackground = slot?.visualMetadata?.background;\n const slotIsSolid = slotBackground === 'IMAGE_BACKGROUND_SOLID';\n const slotWantsContain = Boolean(slot?.crop?.contained) || slotIsSolid;\n const resolvedObjectFit = slot\n ? slotWantsContain\n ? 'contain'\n : 'cover'\n : effectiveObjectFit;\n const resolvedBackgroundColor = slot\n ? slotWantsContain\n ? backdropColorOf(slot)\n : undefined\n : adaptiveBackgroundColor;\n\n useEffect(() => {\n if (!adaptiveActive) {\n return;\n }\n setBackdrop(null);\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n // No analyzable primary source → behave like a busy scene (cover).\n setBackdrop({ hasBackdrop: false });\n return;\n }\n const token = { aborted: false };\n loadBackdropAnalysis(effectiveFilename, token).then((result) => {\n if (!token.aborted) {\n setBackdrop(result);\n }\n });\n return () => {\n token.aborted = true;\n };\n }, [\n adaptiveActive,\n effectiveFilename,\n isFallback,\n cachedFallbackUrl,\n isLowResResolvedAsset,\n fallbackOnLowRes,\n ]);\n\n useEffect(() => {\n setIsLoaded(false);\n setIsError(false);\n setIsFallback(false);\n setImageUrl('');\n }, [sourceCacheKey, fallbackOnLowRes]);\n\n useEffect(() => {\n if (!sourceCacheKey || !fallbackOnLowRes) {\n return;\n }\n\n if (isLowResResolvedAsset) {\n lowResFallbackCache.set(sourceCacheKey, fallbackOnLowRes);\n setIsFallback(true);\n setImageUrl(fallbackOnLowRes);\n return;\n }\n\n if (!cachedFallbackUrl) {\n return;\n }\n\n setIsFallback(true);\n setImageUrl(cachedFallbackUrl);\n }, [\n cachedFallbackUrl,\n fallbackOnLowRes,\n isLowResResolvedAsset,\n sourceCacheKey,\n ]);\n\n // For fill layout, measure container so we can request a resized Wix image.\n useEffect(() => {\n if (layout === 'intrinsic' || disableResize) {\n return;\n }\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n const updateDimensions = () => {\n const rect = container.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n setDimensions({\n width: Math.ceil(rect.width),\n height: Math.ceil(rect.height),\n });\n }\n };\n\n updateDimensions();\n\n if ('ResizeObserver' in window) {\n observerRef.current = new ResizeObserver(() => {\n updateDimensions();\n });\n observerRef.current.observe(container);\n }\n\n const handleResize = () => updateDimensions();\n window.addEventListener('resize', handleResize);\n\n return () => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n window.removeEventListener('resize', handleResize);\n };\n }, [disableResize, layout]);\n\n // Intrinsic layout: use normalized URL without resize logic.\n useEffect(() => {\n if (layout !== 'intrinsic') {\n return;\n }\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n setImageUrl(normalizedUrl);\n }, [\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // External non-Wix URLs skip resizing; use them immediately.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n const isExternalNonWixUrl =\n (normalizedUrl.startsWith('http://') ||\n normalizedUrl.startsWith('https://')) &&\n !normalizedUrl.includes('static.wixstatic.com');\n\n if (isExternalNonWixUrl) {\n setImageUrl(normalizedUrl);\n }\n }, [\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // Explicit opt-out: use original normalized URL even in fill layouts.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (\n !disableResize ||\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n setImageUrl(normalizeImageUrl(effectiveFilename));\n }, [\n cachedFallbackUrl,\n disableResize,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // Wix/local URLs: wait for dimensions, then build a resized URL.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (disableResize) {\n return;\n }\n // Hold the fetch until adaptive analysis picks fit vs fill, so we request\n // the right CDN mode once instead of fetching twice and re-cropping.\n if (!adaptiveResolved) {\n return;\n }\n if (\n !dimensions ||\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n const isExternalNonWixUrl =\n (normalizedUrl.startsWith('http://') ||\n normalizedUrl.startsWith('https://')) &&\n !normalizedUrl.includes('static.wixstatic.com');\n\n if (isExternalNonWixUrl) {\n return;\n }\n\n let imageFilename = effectiveFilename;\n if (normalizedUrl.includes('static.wixstatic.com')) {\n const match = normalizedUrl.match(/media\\/([^/?]+)/);\n if (match && match[1]) {\n imageFilename = match[1];\n } else {\n setImageUrl(normalizedUrl);\n return;\n }\n } else if (\n normalizedUrl.startsWith('https://static.wixstatic.com/media/')\n ) {\n imageFilename = normalizedUrl.replace(\n 'https://static.wixstatic.com/media/',\n '',\n );\n }\n\n const dpr = window.devicePixelRatio ?? 1;\n const optimizedUrl = getResizedImageUrl(\n imageFilename,\n dimensions.width * dpr,\n dimensions.height * dpr,\n { quality, imageMode: effectiveImageMode },\n );\n setImageUrl(optimizedUrl);\n }, [\n dimensions,\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n disableResize,\n quality,\n effectiveImageMode,\n adaptiveResolved,\n ]);\n\n const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const img = e.currentTarget;\n\n if (\n fallbackOnLowRes &&\n !isFallback &&\n !hasResolvedSourceDimensions &&\n (img.naturalWidth < lowResThreshold ||\n img.naturalHeight < lowResThreshold)\n ) {\n const originalImageUrl =\n img.currentSrc || imageUrl || effectiveFilename || sourceIdentifier;\n\n if (sourceCacheKey) {\n lowResFallbackCache.set(sourceCacheKey, fallbackOnLowRes);\n }\n\n setIsFallback(true);\n setImageUrl(fallbackOnLowRes);\n setIsLoaded(false);\n\n console.warn('OptimizedImage applied a low-res fallback image.', {\n originalImageUrl,\n fallbackImageUrl: fallbackOnLowRes,\n naturalWidth: img.naturalWidth,\n naturalHeight: img.naturalHeight,\n lowResThreshold,\n });\n\n deps.logContextualImage?.({\n imageUrl: fallbackOnLowRes,\n contextualTerm: contextualImageKey,\n imageEventType: 'web5_image_fallback',\n isImageResolved: true,\n isFallbackImage: true,\n });\n return;\n }\n\n setIsLoaded(true);\n onLoad?.();\n };\n\n const handleImageError = () => {\n setIsError(true);\n console.error('Failed to load image:', {\n source: sourceIdentifier,\n imageUrl,\n alt,\n });\n onError?.(new Error(`Failed to load image: ${sourceIdentifier}`));\n };\n\n const isTransparentPlaceholder = placeholderColor === 'transparent';\n const placeholderStyles = isTransparentPlaceholder\n ? {\n background: 'transparent',\n animation: 'none',\n }\n : {\n background: `linear-gradient(90deg, ${placeholderColor} 0%, #e5e7eb 50%, ${placeholderColor} 100%)`,\n backgroundSize: '200% 100%',\n animation: 'shimmer 2s infinite ease-in-out',\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick();\n }\n };\n\n return (\n <>\n <span\n ref={containerRef}\n className={[\n 'block',\n layout === 'intrinsic' ? '' : 'relative overflow-hidden',\n showDynamicBadge ? 'relative' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n style={\n layout === 'intrinsic'\n ? undefined\n : { minHeight: '1px', backgroundColor: resolvedBackgroundColor }\n }\n onClick={clickable ? onClick : undefined}\n onKeyDown={clickable ? handleKeyDown : undefined}\n tabIndex={clickable ? 0 : undefined}\n role={clickable ? 'button' : undefined}\n aria-label={clickable ? alt : undefined}\n >\n {showDynamicBadge && (\n <span className=\"absolute left-2 top-2 z-10 rounded bg-blue-600/90 px-2 py-1 text-[10px] font-semibold tracking-wide text-white shadow block\">\n {contextualImageKey}\n </span>\n )}\n {!isLoaded && !isError && sourceIdentifier && (\n <span\n className=\"absolute inset-0 rounded block\"\n style={placeholderStyles}\n role=\"status\"\n aria-label=\"Loading image\"\n />\n )}\n\n {isError && (\n <span\n className=\"absolute inset-0 flex items-center justify-center bg-gray-100 text-gray-500 rounded block\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n <span className=\"text-center p-4 block\">\n <svg\n className=\"w-8 h-8 mx-auto mb-2 text-gray-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n <span className=\"text-xs block\">Failed to load image</span>\n </span>\n </span>\n )}\n\n {imageUrl && !isError && (\n <img\n src={imageUrl}\n alt={alt}\n className={`${\n layout === 'intrinsic'\n ? 'w-full h-auto'\n : 'absolute inset-0 w-full h-full'\n } transition-opacity duration-300 ${\n isLoaded ? 'opacity-100' : 'opacity-0'\n } ${\n resolvedObjectFit === 'contain'\n ? 'object-contain'\n : resolvedObjectFit === 'cover'\n ? 'object-cover'\n : resolvedObjectFit === 'fill'\n ? 'object-fill'\n : resolvedObjectFit === 'none'\n ? 'object-none'\n : resolvedObjectFit === 'scale-down'\n ? 'object-scale-down'\n : ''\n }`}\n style={\n layout === 'intrinsic'\n ? {\n width: '100%',\n height: 'auto',\n maxWidth: '100%',\n display: 'block',\n }\n : {\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: resolvedObjectFit,\n maxWidth: '100%',\n display: 'block',\n }\n }\n onLoad={handleImageLoad}\n onError={handleImageError}\n loading={imageUrl.startsWith('data:') ? 'eager' : 'lazy'}\n />\n )}\n </span>\n </>\n );\n});\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,221 @@
1
+ import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { MAX_IMAGE_SLOTS_PER_SET } from '../image/imageSetTypes.js';
3
+
4
+ /**
5
+ * The page-level image collector (ADR 0221).
6
+ *
7
+ * Sections declare slots; this provider resolves them together. That is the
8
+ * whole point: a per-component call to a set-level endpoint is a one-slot
9
+ * request, which throws away the page-wide assignment, the per-kind mode
10
+ * negotiation and the cohesion that are the only reasons the endpoint takes a
11
+ * list at all.
12
+ *
13
+ * The shape is deliberately one-shot per turn. Slots register during the
14
+ * commit that renders them, the provider flushes once on the next microtask,
15
+ * and a slot that registers after that flush does NOT trigger a second
16
+ * request — it reports `late` and the caller falls back. A page that asks
17
+ * twice cannot be coherent, so asking twice is not offered.
18
+ */
19
+
20
+ const IDLE_STATE = {
21
+ status: 'unavailable'
22
+ };
23
+ const ImageSetContext = /*#__PURE__*/createContext(null);
24
+ export const ImageSetProvider = _ref => {
25
+ let {
26
+ resolveImageSet,
27
+ preferredMode,
28
+ turnId,
29
+ maxSlots = MAX_IMAGE_SLOTS_PER_SET,
30
+ children
31
+ } = _ref;
32
+ // Registration is refs, not state: a slot registering must not re-render the
33
+ // whole tree mid-collection. Subscribers are notified explicitly once the
34
+ // answer lands.
35
+ const pending = useRef(new Map());
36
+ const states = useRef(new Map());
37
+ const listeners = useRef(new Set());
38
+ const flushed = useRef(false);
39
+ const scheduled = useRef(false);
40
+ const notify = useCallback(() => {
41
+ listeners.current.forEach(l => l());
42
+ }, []);
43
+
44
+ // A new turn retires the previous set wholesale — and this MUST happen in the
45
+ // render phase, not an effect. Child effects run before the parent's, so a
46
+ // reset in `useEffect` would fire *after* this turn's slots had registered
47
+ // and would wipe them, leaving the flush with nothing to send. The parent
48
+ // renders first, so resetting here lands before any child registers.
49
+ const lastTurn = useRef(turnId);
50
+ const started = useRef(false);
51
+ if (!started.current) {
52
+ started.current = true;
53
+ } else if (lastTurn.current !== turnId) {
54
+ lastTurn.current = turnId;
55
+ pending.current.clear();
56
+ states.current.clear();
57
+ flushed.current = false;
58
+ scheduled.current = false;
59
+ }
60
+ const flush = useCallback(async () => {
61
+ scheduled.current = false;
62
+ if (flushed.current || !resolveImageSet) {
63
+ return;
64
+ }
65
+ const images = Array.from(pending.current.values());
66
+ if (images.length === 0) {
67
+ return;
68
+ }
69
+ // Everything registered before the flush goes in one request; the set is
70
+ // closed the moment it is sent.
71
+ flushed.current = true;
72
+ try {
73
+ const result = await resolveImageSet({
74
+ images,
75
+ preferredMode
76
+ });
77
+ const bySlotId = new Map();
78
+ // The contract guarantees same length and order, but keying by the echoed
79
+ // slotId means a server that ever breaks that does not silently
80
+ // mis-attribute someone else's photograph to this slot.
81
+ (result.slots ?? []).forEach((s, i) => {
82
+ var _images$i;
83
+ const id = s.slotId || ((_images$i = images[i]) == null ? void 0 : _images$i.id);
84
+ if (id) {
85
+ bySlotId.set(id, s);
86
+ }
87
+ });
88
+ images.forEach(req => {
89
+ const slot = bySlotId.get(req.id);
90
+ states.current.set(req.id, {
91
+ status: slot ? 'resolved' : 'failed',
92
+ slot,
93
+ mode: result.mode
94
+ });
95
+ });
96
+ } catch {
97
+ // A failed set is not a broken page: every slot falls back to whatever it
98
+ // rendered before.
99
+ images.forEach(req => {
100
+ states.current.set(req.id, {
101
+ status: 'failed'
102
+ });
103
+ });
104
+ }
105
+ notify();
106
+ }, [resolveImageSet, preferredMode, notify]);
107
+ const register = useCallback(slot => {
108
+ var _states$current$get2;
109
+ if (!resolveImageSet) {
110
+ return;
111
+ }
112
+ if (flushed.current) {
113
+ var _states$current$get;
114
+ // The set has already gone. Say so rather than open a second one.
115
+ if (((_states$current$get = states.current.get(slot.id)) == null ? void 0 : _states$current$get.status) !== 'resolved') {
116
+ states.current.set(slot.id, {
117
+ status: 'late'
118
+ });
119
+ notify();
120
+ }
121
+ return;
122
+ }
123
+ if (!pending.current.has(slot.id) && pending.current.size >= maxSlots) {
124
+ states.current.set(slot.id, {
125
+ status: 'late'
126
+ });
127
+ notify();
128
+ return;
129
+ }
130
+ pending.current.set(slot.id, slot);
131
+ if (((_states$current$get2 = states.current.get(slot.id)) == null ? void 0 : _states$current$get2.status) !== 'resolved') {
132
+ states.current.set(slot.id, {
133
+ status: 'pending'
134
+ });
135
+ }
136
+ if (!scheduled.current) {
137
+ scheduled.current = true;
138
+ // One microtask after the commit that declared the slots: late enough
139
+ // that every section on the page has registered, early enough that the
140
+ // request is in flight before paint.
141
+ queueMicrotask(() => {
142
+ void flush();
143
+ });
144
+ }
145
+ }, [resolveImageSet, maxSlots, flush, notify]);
146
+ const unregister = useCallback(id => {
147
+ // Only meaningful before the flush; afterwards the set is immutable.
148
+ if (!flushed.current) {
149
+ pending.current.delete(id);
150
+ states.current.delete(id);
151
+ }
152
+ }, []);
153
+ const stateOf = useCallback(id => states.current.get(id) ?? (resolveImageSet ? {
154
+ status: 'pending'
155
+ } : IDLE_STATE), [resolveImageSet]);
156
+ const subscribe = useCallback(listener => {
157
+ listeners.current.add(listener);
158
+ return () => {
159
+ listeners.current.delete(listener);
160
+ };
161
+ }, []);
162
+ const value = useMemo(() => ({
163
+ register,
164
+ unregister,
165
+ stateOf,
166
+ subscribe,
167
+ enabled: Boolean(resolveImageSet)
168
+ }), [register, unregister, stateOf, subscribe, resolveImageSet]);
169
+ return /*#__PURE__*/React.createElement(ImageSetContext.Provider, {
170
+ value: value
171
+ }, children);
172
+ };
173
+
174
+ /**
175
+ * Declare one image slot and read back what the page-wide resolution gave it.
176
+ *
177
+ * Returns `{ status: 'unavailable' }` when no provider or no resolver is
178
+ * present — storybook, tests, a host that has not adopted this yet — so a
179
+ * caller can render its existing image path unchanged in that case rather than
180
+ * guard on the provider's existence.
181
+ *
182
+ * `request` is read on every render but only its identity matters for
183
+ * re-registration, so callers should memoise it (or let its fields be stable).
184
+ */
185
+ export function useImageSlot(request) {
186
+ const ctx = useContext(ImageSetContext);
187
+ const [, forceRender] = useState(0);
188
+ const id = request == null ? void 0 : request.id;
189
+
190
+ // Subscribe BEFORE registering, and keep that order: registering can settle
191
+ // this slot synchronously — a slot arriving after the set was already sent is
192
+ // told `late` inside `register` itself — and a notification fired before this
193
+ // component was listening would be lost, leaving it stuck on `pending`
194
+ // forever. Effects run in declaration order, so this one wins.
195
+ useEffect(() => {
196
+ if (!ctx) {
197
+ return undefined;
198
+ }
199
+ return ctx.subscribe(() => forceRender(n => n + 1));
200
+ }, [ctx]);
201
+ useEffect(() => {
202
+ if (!ctx || !request) {
203
+ return undefined;
204
+ }
205
+ ctx.register(request);
206
+ return () => ctx.unregister(request.id);
207
+ // Registration is keyed on the slot's own content: a section that changes
208
+ // what it is asking for re-declares, one that merely re-renders does not.
209
+ }, [ctx, request == null ? void 0 : request.id, request == null ? void 0 : request.kind, request == null ? void 0 : request.ratio, request == null ? void 0 : request.semantic, request == null ? void 0 : request.entityId, request == null ? void 0 : request.renderWidthPx]);
210
+ if (!ctx || !id) {
211
+ return IDLE_STATE;
212
+ }
213
+ return ctx.stateOf(id);
214
+ }
215
+
216
+ /** True when a host has wired a resolver — i.e. slots will actually resolve. */
217
+ export function useImageSetEnabled() {
218
+ var _useContext;
219
+ return ((_useContext = useContext(ImageSetContext)) == null ? void 0 : _useContext.enabled) ?? false;
220
+ }
221
+ //# sourceMappingURL=ImageSetContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["React","createContext","useCallback","useContext","useEffect","useMemo","useRef","useState","MAX_IMAGE_SLOTS_PER_SET","IDLE_STATE","status","ImageSetContext","ImageSetProvider","_ref","resolveImageSet","preferredMode","turnId","maxSlots","children","pending","Map","states","listeners","Set","flushed","scheduled","notify","current","forEach","l","lastTurn","started","clear","flush","images","Array","from","values","length","result","bySlotId","slots","s","i","_images$i","id","slotId","set","req","slot","get","mode","register","_states$current$get2","_states$current$get","has","size","queueMicrotask","unregister","delete","stateOf","subscribe","listener","add","value","enabled","Boolean","createElement","Provider","useImageSlot","request","ctx","forceRender","undefined","n","kind","ratio","semantic","entityId","renderWidthPx","useImageSetEnabled","_useContext"],"sources":["../../../src/context/ImageSetContext.tsx"],"sourcesContent":["import React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type FC,\n type PropsWithChildren,\n} from 'react';\nimport {\n MAX_IMAGE_SLOTS_PER_SET,\n type ImageBackground,\n type ImageSlot,\n type ResolveImageSetResult,\n type ResolvedImageSlot,\n} from '../image/imageSetTypes';\n\n/**\n * The page-level image collector (ADR 0221).\n *\n * Sections declare slots; this provider resolves them together. That is the\n * whole point: a per-component call to a set-level endpoint is a one-slot\n * request, which throws away the page-wide assignment, the per-kind mode\n * negotiation and the cohesion that are the only reasons the endpoint takes a\n * list at all.\n *\n * The shape is deliberately one-shot per turn. Slots register during the\n * commit that renders them, the provider flushes once on the next microtask,\n * and a slot that registers after that flush does NOT trigger a second\n * request — it reports `late` and the caller falls back. A page that asks\n * twice cannot be coherent, so asking twice is not offered.\n */\n\ntype SlotStatus =\n /** No resolver available — the host did not supply one. */\n | 'unavailable'\n /** Registered, waiting for the flush or the response. */\n | 'pending'\n /** The response arrived and carried this slot. */\n | 'resolved'\n /** Registered after the set had already been sent; take the old path. */\n | 'late'\n /** The request failed. */\n | 'failed';\n\nexport interface ImageSlotState {\n status: SlotStatus;\n slot?: ResolvedImageSlot;\n /** The mode the whole set settled on, once known. */\n mode?: ImageBackground;\n}\n\ninterface ImageSetContextValue {\n register: (slot: ImageSlot) => void;\n unregister: (id: string) => void;\n stateOf: (id: string) => ImageSlotState;\n subscribe: (listener: () => void) => () => void;\n enabled: boolean;\n}\n\nconst IDLE_STATE: ImageSlotState = { status: 'unavailable' };\n\nconst ImageSetContext = createContext<ImageSetContextValue | null>(null);\n\nexport interface ImageSetProviderProps extends PropsWithChildren {\n /**\n * Resolves one set. Supplied by the host rather than called directly, so core\n * carries no transport: see `ComponentDependencies.resolveImageSet`.\n */\n resolveImageSet?: (input: {\n images: ImageSlot[];\n preferredMode?: ImageBackground;\n }) => Promise<ResolveImageSetResult>;\n /** Passed through as a tie-break term; never a gate (ADR 0197). */\n preferredMode?: ImageBackground;\n /**\n * Identifies the turn. Changing it starts a new set — the previous turn's\n * results are dropped and slots re-register. Without it the provider resolves\n * exactly once for its lifetime.\n */\n turnId?: string;\n /**\n * Slots beyond this many are refused with `late` rather than silently\n * dropped by the server. Defaults to the contract's own cap of 24.\n */\n maxSlots?: number;\n}\n\nexport const ImageSetProvider: FC<ImageSetProviderProps> = ({\n resolveImageSet,\n preferredMode,\n turnId,\n maxSlots = MAX_IMAGE_SLOTS_PER_SET,\n children,\n}) => {\n // Registration is refs, not state: a slot registering must not re-render the\n // whole tree mid-collection. Subscribers are notified explicitly once the\n // answer lands.\n const pending = useRef(new Map<string, ImageSlot>());\n const states = useRef(new Map<string, ImageSlotState>());\n const listeners = useRef(new Set<() => void>());\n const flushed = useRef(false);\n const scheduled = useRef(false);\n\n const notify = useCallback(() => {\n listeners.current.forEach((l) => l());\n }, []);\n\n // A new turn retires the previous set wholesale — and this MUST happen in the\n // render phase, not an effect. Child effects run before the parent's, so a\n // reset in `useEffect` would fire *after* this turn's slots had registered\n // and would wipe them, leaving the flush with nothing to send. The parent\n // renders first, so resetting here lands before any child registers.\n const lastTurn = useRef<string | undefined>(turnId);\n const started = useRef(false);\n if (!started.current) {\n started.current = true;\n } else if (lastTurn.current !== turnId) {\n lastTurn.current = turnId;\n pending.current.clear();\n states.current.clear();\n flushed.current = false;\n scheduled.current = false;\n }\n\n const flush = useCallback(async () => {\n scheduled.current = false;\n if (flushed.current || !resolveImageSet) {\n return;\n }\n const images = Array.from(pending.current.values());\n if (images.length === 0) {\n return;\n }\n // Everything registered before the flush goes in one request; the set is\n // closed the moment it is sent.\n flushed.current = true;\n\n try {\n const result = await resolveImageSet({ images, preferredMode });\n const bySlotId = new Map<string, ResolvedImageSlot>();\n // The contract guarantees same length and order, but keying by the echoed\n // slotId means a server that ever breaks that does not silently\n // mis-attribute someone else's photograph to this slot.\n (result.slots ?? []).forEach((s, i) => {\n const id = s.slotId || images[i]?.id;\n if (id) {\n bySlotId.set(id, s);\n }\n });\n images.forEach((req) => {\n const slot = bySlotId.get(req.id);\n states.current.set(req.id, {\n status: slot ? 'resolved' : 'failed',\n slot,\n mode: result.mode,\n });\n });\n } catch {\n // A failed set is not a broken page: every slot falls back to whatever it\n // rendered before.\n images.forEach((req) => {\n states.current.set(req.id, { status: 'failed' });\n });\n }\n notify();\n }, [resolveImageSet, preferredMode, notify]);\n\n const register = useCallback(\n (slot: ImageSlot) => {\n if (!resolveImageSet) {\n return;\n }\n if (flushed.current) {\n // The set has already gone. Say so rather than open a second one.\n if (states.current.get(slot.id)?.status !== 'resolved') {\n states.current.set(slot.id, { status: 'late' });\n notify();\n }\n return;\n }\n if (!pending.current.has(slot.id) && pending.current.size >= maxSlots) {\n states.current.set(slot.id, { status: 'late' });\n notify();\n return;\n }\n pending.current.set(slot.id, slot);\n if (states.current.get(slot.id)?.status !== 'resolved') {\n states.current.set(slot.id, { status: 'pending' });\n }\n if (!scheduled.current) {\n scheduled.current = true;\n // One microtask after the commit that declared the slots: late enough\n // that every section on the page has registered, early enough that the\n // request is in flight before paint.\n queueMicrotask(() => {\n void flush();\n });\n }\n },\n [resolveImageSet, maxSlots, flush, notify],\n );\n\n const unregister = useCallback((id: string) => {\n // Only meaningful before the flush; afterwards the set is immutable.\n if (!flushed.current) {\n pending.current.delete(id);\n states.current.delete(id);\n }\n }, []);\n\n const stateOf = useCallback(\n (id: string): ImageSlotState =>\n states.current.get(id) ??\n (resolveImageSet ? { status: 'pending' } : IDLE_STATE),\n [resolveImageSet],\n );\n\n const subscribe = useCallback((listener: () => void) => {\n listeners.current.add(listener);\n return () => {\n listeners.current.delete(listener);\n };\n }, []);\n\n const value = useMemo<ImageSetContextValue>(\n () => ({\n register,\n unregister,\n stateOf,\n subscribe,\n enabled: Boolean(resolveImageSet),\n }),\n [register, unregister, stateOf, subscribe, resolveImageSet],\n );\n\n return (\n <ImageSetContext.Provider value={value}>\n {children}\n </ImageSetContext.Provider>\n );\n};\n\n/**\n * Declare one image slot and read back what the page-wide resolution gave it.\n *\n * Returns `{ status: 'unavailable' }` when no provider or no resolver is\n * present — storybook, tests, a host that has not adopted this yet — so a\n * caller can render its existing image path unchanged in that case rather than\n * guard on the provider's existence.\n *\n * `request` is read on every render but only its identity matters for\n * re-registration, so callers should memoise it (or let its fields be stable).\n */\nexport function useImageSlot(request: ImageSlot | undefined): ImageSlotState {\n const ctx = useContext(ImageSetContext);\n const [, forceRender] = useState(0);\n\n const id = request?.id;\n\n // Subscribe BEFORE registering, and keep that order: registering can settle\n // this slot synchronously — a slot arriving after the set was already sent is\n // told `late` inside `register` itself — and a notification fired before this\n // component was listening would be lost, leaving it stuck on `pending`\n // forever. Effects run in declaration order, so this one wins.\n useEffect(() => {\n if (!ctx) {\n return undefined;\n }\n return ctx.subscribe(() => forceRender((n) => n + 1));\n }, [ctx]);\n\n useEffect(() => {\n if (!ctx || !request) {\n return undefined;\n }\n ctx.register(request);\n return () => ctx.unregister(request.id);\n // Registration is keyed on the slot's own content: a section that changes\n // what it is asking for re-declares, one that merely re-renders does not.\n }, [\n ctx,\n request?.id,\n request?.kind,\n request?.ratio,\n request?.semantic,\n request?.entityId,\n request?.renderWidthPx,\n ]);\n\n if (!ctx || !id) {\n return IDLE_STATE;\n }\n return ctx.stateOf(id);\n}\n\n/** True when a host has wired a resolver — i.e. slots will actually resolve. */\nexport function useImageSetEnabled(): boolean {\n return useContext(ImageSetContext)?.enabled ?? false;\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The `ResolveImageSet` contract, as the client sees it.
3
+ *
4
+ * Mirrors `wix.enterprise.web_five.v1.ImageService/ResolveImageSet` (ADR 0143,
5
+ * 0148, 0187, 0197, 0220). Declared here rather than imported from the
6
+ * ambassador package so core carries no transport dependency: the host supplies
7
+ * `ComponentDependencies.resolveImageSet` and owns the wire mapping, including
8
+ * the fact that responses come back `preserving_proto_field_name`.
9
+ *
10
+ * String unions rather than enums so the values are exactly the wire's and no
11
+ * runtime object ships with them.
12
+ */
13
+
14
+ /** How an image sits on its background. */
15
+
16
+ /** What a slot is for, which decides what it may name and how it is scored. */
17
+
18
+ /**
19
+ * How honestly a slot's image answers what the slot asked for.
20
+ *
21
+ * Measured, and load-bearing: this is the ONLY field that separates a hit from
22
+ * a miss. `score` cannot do it — a FALLBACK has been observed at 0.995 against
23
+ * a correct EXACT at 0.86 in the same response — and neither field may be
24
+ * compared across responses, because both are properties of the page-wide
25
+ * assignment rather than of the slot. The same entity slot resolving to the
26
+ * same image with a byte-identical crop has come back DEGRADED in one request
27
+ * and EXACT in another, differing only in what else shared the page.
28
+ */
29
+
30
+ /** A crop in source pixels of the image the slot was given. */
31
+
32
+ /** A box in normalised [0,1] coordinates — a fraction of the image's own size. */
33
+
34
+ /** A square grid of per-cell values, `edge` on a side. */
35
+
36
+ /**
37
+ * The feature record the resolver already decoded for its own scoring, returned
38
+ * rather than discarded (ADR 0187).
39
+ */
40
+
41
+ /** One slot a section declares. */
42
+
43
+ /** The set's own cap — 24 slots per request, per the proto's `maxSize`. */
44
+ export const MAX_IMAGE_SLOTS_PER_SET = 24;
45
+
46
+ /** What the server substitutes for an absent or zero `renderWidthPx`. */
47
+ export const DEFAULT_RENDER_WIDTH_PX = 800;
48
+
49
+ /**
50
+ * Above this, `MediaTransformUrl` refuses to compose the crop and returns the
51
+ * uncropped original. Callers should clamp rather than let a device-pixel-ratio
52
+ * multiplication carry them past it.
53
+ */
54
+ export const MAX_RENDER_WIDTH_PX = 5000;
55
+
56
+ /** True when the slot carries a usable image (as opposed to a hole). */
57
+ export function hasSlotImage(slot) {
58
+ return Boolean(slot == null ? void 0 : slot.imageUrl);
59
+ }
60
+
61
+ /**
62
+ * True when the slot's image actually depicts what the slot asked for.
63
+ *
64
+ * FALLBACK means the resolver had nothing for this subject and returned
65
+ * something rather than a hole — legitimate for a decorative slot, wrong for
66
+ * one captioned as a particular product.
67
+ */
68
+ export function isSubjectMatch(slot) {
69
+ return (slot == null ? void 0 : slot.match) === 'IMAGE_MATCH_QUALITY_EXACT' || (slot == null ? void 0 : slot.match) === 'IMAGE_MATCH_QUALITY_DEGRADED';
70
+ }
71
+
72
+ /**
73
+ * The colour to paint behind a `contain`-fitted image so its own backdrop does
74
+ * not read as a rectangle against the surface. `backgroundColor` when the image
75
+ * is SOLID, the dominant palette swatch otherwise, and `undefined` when neither
76
+ * is known.
77
+ */
78
+ export function backdropColorOf(slot) {
79
+ var _meta$palette;
80
+ const meta = slot == null ? void 0 : slot.visualMetadata;
81
+ if (!meta) {
82
+ return undefined;
83
+ }
84
+ return meta.backgroundColor || ((_meta$palette = meta.palette) == null ? void 0 : _meta$palette.dominant) || undefined;
85
+ }
86
+
87
+ /**
88
+ * The aspect ratio to render `imageUrl` at. Normally the slot's requested
89
+ * ratio, but a contained crop (ADR 0220) deliberately returns another, and the
90
+ * URL's own `fill/w_,h_` carries that one — so honouring `crop.ratio` is what
91
+ * keeps the picture undistorted.
92
+ */
93
+ export function renderRatioOf(slot, requestedRatio) {
94
+ const crop = slot == null ? void 0 : slot.crop;
95
+ if (crop != null && crop.contained && crop.ratio && crop.ratio > 0) {
96
+ return crop.ratio;
97
+ }
98
+ return requestedRatio;
99
+ }
100
+ //# sourceMappingURL=imageSetTypes.js.map