/** * `next/image` shim — environment-aware Image component. * * Defaults to a plain `` so non-Next hosts (Vite, CRA, esbuild) * work out of the box without aliasing tricks. Next.js hosts can opt * into the REAL `next/image` (with Image Optimization) by calling * {@link registerImage} ONCE at app init: * * // hub: lib/embed-shim-registration.ts * import NextImage from 'next/image' * import { registerImage } from '@flamingo-stack/openframe-frontend-core/embed-shims' * registerImage(NextImage) * * After registration, every lib component that renders this shim * delegates to `NextImage` — full Image Optimization, blur placeholders, * priority, etc. Without registration, the shim falls through to the * plain `` path that drops Next-specific props. * * Lib internals import this shim directly (relative path); hub-side * code goes through the barrel (`@flamingo-stack/.../embed-shims`). */ 'use client' import { forwardRef, type ComponentType, type ImgHTMLAttributes, } from 'react' type ImageProps = Omit, 'src' | 'placeholder'> & { src?: string | { src: string } alt?: string width?: number | string height?: number | string fill?: boolean sizes?: string quality?: number priority?: boolean loading?: 'eager' | 'lazy' placeholder?: 'blur' | 'empty' | `data:image/${string}` blurDataURL?: string unoptimized?: boolean onLoadingComplete?: (img: HTMLImageElement) => void loader?: unknown } let impl: ComponentType | null = null /** * Register the real `next/image` so this shim delegates to it instead * of rendering a plain ``. Call ONCE at app init in a Next.js host * — subsequent calls overwrite the registration silently. Safe to skip * entirely in non-Next environments. */ export function registerImage(component: ComponentType): void { impl = component } const Image = forwardRef(function NextImageShim( props, ref, ) { // Real impl path — registered by the host. Hand off untouched so // every Next-specific prop (priority, placeholder, sizes, loader…) // reaches the real component intact. if (impl) { const Real = impl return } // Fallback path — plain . Drops Next-only props and reduces // `StaticImageData` src to a string. const { src, alt, width, height, fill, sizes: _sizes, quality: _quality, priority: _priority, placeholder: _placeholder, blurDataURL: _blurDataURL, unoptimized: _unoptimized, onLoadingComplete, loader: _loader, style, ...rest } = props const srcStr = typeof src === 'string' ? src : src?.src const finalStyle = fill ? { position: 'absolute' as const, inset: 0, width: '100%', height: '100%', objectFit: 'cover' as const, ...style, } : style return ( {alt onLoadingComplete(e.currentTarget) : undefined} {...rest} /> ) }) export default Image