@joycostudio/iris 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/next.js ADDED
@@ -0,0 +1,352 @@
1
+ "use client";
2
+
3
+ // packages/next/index.tsx
4
+ import NextImage, { getImageProps } from "next/image.js";
5
+ import {
6
+ useCallback,
7
+ useEffect,
8
+ useLayoutEffect,
9
+ useRef,
10
+ useState
11
+ } from "react";
12
+
13
+ // packages/core/placeholder.ts
14
+ function placeholderSettings(input) {
15
+ if (!input || input === "none") return null;
16
+ const options = input === "blur" ? {} : input;
17
+ return {
18
+ width: options.width ?? 10,
19
+ quality: options.quality ?? 40,
20
+ blur: options.blur ?? 12
21
+ };
22
+ }
23
+ function resolvePlaceholder({
24
+ asset,
25
+ placeholder
26
+ }) {
27
+ const settings = placeholderSettings(placeholder);
28
+ if (!settings) return null;
29
+ const url = asset.placeholders?.find(
30
+ (item) => item.width === settings.width && item.quality === settings.quality
31
+ )?.url ?? (settings.width === 10 && settings.quality === 40 ? asset.placeholder : void 0);
32
+ return url ? { url, blur: settings.blur } : null;
33
+ }
34
+
35
+ // packages/next/index.tsx
36
+ import { useIrisDebugger } from "@joycostudio/iris/react";
37
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
38
+ var Image = "default" in NextImage ? NextImage.default : NextImage;
39
+ var EMPTY_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";
40
+ function isEmptyImage(image) {
41
+ return (image.currentSrc || image.src) === EMPTY_IMAGE;
42
+ }
43
+ function createIrisNextImage({
44
+ iris,
45
+ catalog
46
+ }) {
47
+ return function IrisImage({
48
+ recipe: override,
49
+ usage,
50
+ ...props
51
+ }) {
52
+ const src = typeof props.src === "string" ? props.src : props.src && "src" in props.src ? props.src.src : null;
53
+ const asset = src ? catalog[src] : void 0;
54
+ if (!src || !asset)
55
+ return /* @__PURE__ */ jsx(
56
+ Image,
57
+ {
58
+ ...props,
59
+ "data-iris": props["data-iris"] || props.alt || src || void 0
60
+ }
61
+ );
62
+ const contextUsage = {
63
+ width: props.width ? Number(props.width) : void 0,
64
+ ...usage,
65
+ priority: Boolean(props.priority || props.preload || usage?.priority)
66
+ };
67
+ const selected = iris.select({
68
+ asset,
69
+ usage: contextUsage,
70
+ recipe: override
71
+ });
72
+ if (!selected.ok) throw new Error(`Iris: ${selected.error.message}`);
73
+ const { name, recipe } = selected.data;
74
+ const animated = asset.animated || /\.(?:gif|svg)(?:[?#]|$)/i.test(src);
75
+ const direct = props.unoptimized || animated;
76
+ const sizes = props.sizes || (props.width ? `${props.width}px` : "100vw");
77
+ const normalProps = {
78
+ ...props,
79
+ src,
80
+ sizes,
81
+ unoptimized: direct,
82
+ placeholder: "empty"
83
+ };
84
+ const { props: normal } = getImageProps(normalProps);
85
+ const liveProgressive = Boolean(
86
+ normal.src === src && !normal.srcSet && asset.progressive
87
+ );
88
+ const preview = !direct && !liveProgressive ? previewImage({ recipe, asset, props: normalProps }) : void 0;
89
+ const progressiveDelivery = liveProgressive;
90
+ const placeholder = !liveProgressive ? resolvePlaceholder({ asset, placeholder: recipe.placeholder }) : null;
91
+ const previewSettings = recipe.preview && recipe.preview !== "none" ? recipe.preview : null;
92
+ const loadPreviewFirst = Boolean(preview && previewSettings?.loadFirst);
93
+ const key = JSON.stringify([
94
+ src,
95
+ name,
96
+ sizes,
97
+ props.quality,
98
+ previewSettings,
99
+ liveProgressive,
100
+ loadPreviewFirst
101
+ ]);
102
+ return /* @__PURE__ */ jsx(
103
+ DeliveryImage,
104
+ {
105
+ ...normalProps,
106
+ src,
107
+ source: src,
108
+ unoptimized: Boolean(direct),
109
+ recipeName: name,
110
+ preview,
111
+ loadPreviewFirst,
112
+ previewMinDisplayMs: previewSettings?.minDisplayMs ?? 0,
113
+ blurDataURL: placeholder?.url,
114
+ blurRadius: placeholder?.blur ?? 0,
115
+ progressiveDelivery
116
+ },
117
+ key
118
+ );
119
+ };
120
+ }
121
+ function previewImage({
122
+ recipe,
123
+ asset,
124
+ props
125
+ }) {
126
+ const preview = recipe.preview;
127
+ if (!preview || preview === "none") return void 0;
128
+ if (preview.url)
129
+ return preview.url === asset.src ? void 0 : { src: preview.url, unoptimized: true };
130
+ const quality = preview.quality ?? props.quality;
131
+ const { props: candidates } = getImageProps({
132
+ ...props,
133
+ quality,
134
+ width: 1,
135
+ height: 1,
136
+ fill: false,
137
+ sizes: "1px",
138
+ placeholder: "empty",
139
+ overrideSrc: void 0
140
+ });
141
+ const widths = (candidates.srcSet?.split(", ") ?? []).flatMap((item) => {
142
+ const match = item.match(/^(.*) (\d+)w$/);
143
+ return match ? [{ url: match[1], width: Number(match[2]) }] : [];
144
+ });
145
+ if (!widths.length) return void 0;
146
+ const loader = ({ width }) => {
147
+ const cap = preview.ratio === void 0 ? Math.min(width, preview.maxWidth ?? 640) : Math.min(
148
+ Math.floor(width * preview.ratio),
149
+ preview.maxWidth ?? Infinity
150
+ );
151
+ const smaller = widths.filter((candidate) => candidate.width < width);
152
+ const selected = smaller.filter((candidate) => candidate.width <= cap).at(-1) ?? smaller[0] ?? widths[0];
153
+ return selected.url;
154
+ };
155
+ return { src: asset.src, loader, quality, unoptimized: false };
156
+ }
157
+ function DeliveryImage({
158
+ source,
159
+ recipeName,
160
+ preview,
161
+ loadPreviewFirst,
162
+ previewMinDisplayMs,
163
+ progressiveDelivery,
164
+ style,
165
+ onLoad,
166
+ onError,
167
+ onLoadCapture,
168
+ blurDataURL,
169
+ blurRadius,
170
+ ...props
171
+ }) {
172
+ const [loaded, setLoaded] = useState(false);
173
+ const [previewLoaded, setPreviewLoaded] = useState(false);
174
+ const [finalStarted, setFinalStarted] = useState(!loadPreviewFirst);
175
+ const layerZIndex = typeof style?.zIndex === "number" ? style.zIndex : 0;
176
+ const iris = useIrisDebugger();
177
+ const finalRef = useRef(null);
178
+ const previewRef = useRef(null);
179
+ const previewMarked = useRef(false);
180
+ const attachFinal = useCallback((image) => {
181
+ finalRef.current = image;
182
+ if (image?.complete && image.naturalWidth && !isEmptyImage(image))
183
+ setLoaded(true);
184
+ }, []);
185
+ useEffect(() => {
186
+ let timer;
187
+ let observer;
188
+ const startFallback = () => {
189
+ timer ?? (timer = setTimeout(() => setFinalStarted(true), 3e3));
190
+ };
191
+ if (loadPreviewFirst && !finalStarted && previewLoaded) {
192
+ timer = setTimeout(() => setFinalStarted(true), previewMinDisplayMs);
193
+ } else if (loadPreviewFirst && !finalStarted && previewRef.current) {
194
+ if (typeof IntersectionObserver === "undefined") startFallback();
195
+ else {
196
+ observer = new IntersectionObserver((entries) => {
197
+ if (entries.some((entry) => entry.isIntersecting)) {
198
+ startFallback();
199
+ observer?.disconnect();
200
+ }
201
+ });
202
+ observer.observe(previewRef.current);
203
+ }
204
+ }
205
+ return () => {
206
+ observer?.disconnect();
207
+ if (timer) clearTimeout(timer);
208
+ };
209
+ }, [loadPreviewFirst, finalStarted, previewLoaded, previewMinDisplayMs]);
210
+ const revealPreview = useCallback(
211
+ (image) => {
212
+ if (!image.isConnected || !image.naturalWidth || previewMarked.current)
213
+ return;
214
+ previewMarked.current = true;
215
+ setPreviewLoaded(true);
216
+ if (finalRef.current) iris?.markPreview(finalRef.current, image);
217
+ },
218
+ [iris]
219
+ );
220
+ const attachPreview = useCallback(
221
+ (image) => {
222
+ previewRef.current = image;
223
+ if (image?.complete && image.naturalWidth) revealPreview(image);
224
+ },
225
+ [revealPreview]
226
+ );
227
+ useLayoutEffect(() => {
228
+ if (iris && finalRef.current) {
229
+ iris.startTimeline(finalRef.current);
230
+ if (finalRef.current.complete && finalRef.current.naturalWidth && !isEmptyImage(finalRef.current))
231
+ iris.markFinal(finalRef.current);
232
+ }
233
+ }, [iris, source]);
234
+ useLayoutEffect(() => {
235
+ const image = finalRef.current;
236
+ if (finalStarted && image?.complete && image.naturalWidth && !isEmptyImage(image)) {
237
+ setLoaded(true);
238
+ iris?.markFinal(image);
239
+ }
240
+ }, [finalStarted, iris]);
241
+ const layers = /* @__PURE__ */ jsxs(Fragment, { children: [
242
+ /* @__PURE__ */ jsx(
243
+ Image,
244
+ {
245
+ ...props,
246
+ src: finalStarted ? props.src : EMPTY_IMAGE,
247
+ unoptimized: finalStarted ? props.unoptimized : true,
248
+ loader: finalStarted ? props.loader : void 0,
249
+ overrideSrc: finalStarted ? props.overrideSrc : void 0,
250
+ priority: finalStarted ? props.priority : false,
251
+ preload: finalStarted ? props.preload : false,
252
+ fetchPriority: finalStarted ? props.fetchPriority : "auto",
253
+ ref: attachFinal,
254
+ "data-iris": props["data-iris"] || props.alt || source,
255
+ "data-iris-src": source,
256
+ "data-iris-recipe": recipeName,
257
+ "data-iris-delivery": progressiveDelivery ? "progressive" : void 0,
258
+ placeholder: "empty",
259
+ style: {
260
+ ...style,
261
+ ...preview || blurDataURL ? { zIndex: layerZIndex + 2 } : {},
262
+ ...loaded ? { backgroundImage: "none" } : {}
263
+ },
264
+ onLoadCapture: (event) => {
265
+ if (isEmptyImage(event.currentTarget)) return;
266
+ event.currentTarget.style.backgroundImage = "none";
267
+ setLoaded(true);
268
+ onLoadCapture?.(event);
269
+ },
270
+ onLoad: (event) => {
271
+ if (isEmptyImage(event.currentTarget)) return;
272
+ setLoaded(true);
273
+ iris?.markFinal(event.currentTarget);
274
+ onLoad?.(event);
275
+ },
276
+ onError: (event) => {
277
+ if (isEmptyImage(event.currentTarget)) return;
278
+ onError?.(event);
279
+ }
280
+ }
281
+ ),
282
+ preview && !loaded && /* @__PURE__ */ jsx(
283
+ Image,
284
+ {
285
+ ...props,
286
+ src: preview.src,
287
+ loader: preview.loader,
288
+ overrideSrc: void 0,
289
+ ref: attachPreview,
290
+ unoptimized: preview.unoptimized,
291
+ quality: preview.quality,
292
+ fill: true,
293
+ width: void 0,
294
+ height: void 0,
295
+ alt: "",
296
+ "aria-hidden": "true",
297
+ "data-iris": void 0,
298
+ "data-slot": "image-preview",
299
+ priority: false,
300
+ preload: false,
301
+ fetchPriority: "auto",
302
+ placeholder: "empty",
303
+ style: {
304
+ ...style,
305
+ pointerEvents: "none",
306
+ zIndex: layerZIndex + 1,
307
+ backgroundImage: "none"
308
+ },
309
+ onLoadCapture: (event) => revealPreview(event.currentTarget),
310
+ onError: () => setFinalStarted(true)
311
+ }
312
+ ),
313
+ !loaded && !previewLoaded && blurDataURL && /* @__PURE__ */ jsx(
314
+ "span",
315
+ {
316
+ "aria-hidden": "true",
317
+ "data-slot": "image-placeholder",
318
+ "data-stage": "blur",
319
+ style: {
320
+ position: "absolute",
321
+ inset: 0,
322
+ ...style,
323
+ pointerEvents: "none",
324
+ zIndex: layerZIndex,
325
+ backgroundImage: `url(${JSON.stringify(blurDataURL)})`,
326
+ backgroundSize: style?.objectFit === "contain" ? "contain" : style?.objectFit === "cover" ? "cover" : "100% 100%",
327
+ backgroundPosition: style?.objectPosition ?? "center",
328
+ backgroundRepeat: "no-repeat",
329
+ filter: `blur(${blurRadius}px)`,
330
+ clipPath: "inset(0)"
331
+ }
332
+ }
333
+ )
334
+ ] });
335
+ return preview || blurDataURL ? /* @__PURE__ */ jsx(
336
+ "span",
337
+ {
338
+ "data-slot": "iris-image",
339
+ style: props.fill ? { position: "absolute", inset: 0, isolation: "isolate" } : {
340
+ position: "relative",
341
+ display: "inline-grid",
342
+ maxWidth: "100%",
343
+ isolation: "isolate"
344
+ },
345
+ children: layers
346
+ }
347
+ ) : layers;
348
+ }
349
+ export {
350
+ createIrisNextImage
351
+ };
352
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../packages/next/index.tsx","../packages/core/placeholder.ts"],"sourcesContent":["\"use client\"\n\nimport NextImage, { getImageProps, type ImageProps } from \"next/image.js\"\nimport {\n useCallback,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\"\nimport type {\n IrisCatalogAsset,\n IrisPolicy,\n IrisPolicyUsage,\n IrisRecipe,\n} from \"../core\"\nimport { resolvePlaceholder } from \"../core/placeholder\"\nimport { useIrisDebugger } from \"@joycostudio/iris/react\"\n\n// Next exposes a CommonJS entry; native ESM and bundlers unwrap it differently.\nconst Image = (\n \"default\" in NextImage ? NextImage.default : NextImage\n) as typeof NextImage\nconst EMPTY_IMAGE = \"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=\"\n\nfunction isEmptyImage(image: HTMLImageElement) {\n return (image.currentSrc || image.src) === EMPTY_IMAGE\n}\n\nexport type IrisNextImageProps<Custom = Record<string, unknown>> =\n ImageProps & {\n recipe?: string\n usage?: IrisPolicyUsage<Custom>\n \"data-iris\"?: string\n }\n\n/** Next owns responsive widths, optimizer URLs and allowed quality values. */\nexport function createIrisNextImage<Custom>({\n iris,\n catalog,\n}: {\n iris: IrisPolicy<Custom>\n catalog: Readonly<Record<string, IrisCatalogAsset>>\n}) {\n return function IrisImage({\n recipe: override,\n usage,\n ...props\n }: IrisNextImageProps<Custom>) {\n const src =\n typeof props.src === \"string\"\n ? props.src\n : props.src && \"src\" in props.src\n ? props.src.src\n : null\n const asset = src ? catalog[src] : undefined\n if (!src || !asset)\n return (\n <Image\n {...props}\n data-iris={props[\"data-iris\"] || props.alt || src || undefined}\n />\n )\n const contextUsage = {\n width: props.width ? Number(props.width) : undefined,\n ...usage,\n priority: Boolean(props.priority || props.preload || usage?.priority),\n }\n const selected = iris.select({\n asset,\n usage: contextUsage,\n recipe: override,\n })\n if (!selected.ok) throw new Error(`Iris: ${selected.error.message}`)\n const { name, recipe } = selected.data\n const animated = asset.animated || /\\.(?:gif|svg)(?:[?#]|$)/i.test(src)\n const direct = props.unoptimized || animated\n const sizes = props.sizes || (props.width ? `${props.width}px` : \"100vw\")\n const normalProps = {\n ...props,\n src,\n sizes,\n unoptimized: direct,\n placeholder: \"empty\" as const,\n }\n const { props: normal } = getImageProps(normalProps)\n // Source metadata describes the served file only when Next passes it through.\n const liveProgressive = Boolean(\n normal.src === src && !normal.srcSet && asset.progressive\n )\n const preview =\n !direct && !liveProgressive\n ? previewImage({ recipe, asset, props: normalProps })\n : undefined\n const progressiveDelivery = liveProgressive\n const placeholder = !liveProgressive\n ? resolvePlaceholder({ asset, placeholder: recipe.placeholder })\n : null\n const previewSettings =\n recipe.preview && recipe.preview !== \"none\" ? recipe.preview : null\n const loadPreviewFirst = Boolean(preview && previewSettings?.loadFirst)\n const key = JSON.stringify([\n src,\n name,\n sizes,\n props.quality,\n previewSettings,\n liveProgressive,\n loadPreviewFirst,\n ])\n return (\n <DeliveryImage\n key={key}\n {...normalProps}\n src={src}\n source={src}\n unoptimized={Boolean(direct)}\n recipeName={name}\n preview={preview}\n loadPreviewFirst={loadPreviewFirst}\n previewMinDisplayMs={previewSettings?.minDisplayMs ?? 0}\n blurDataURL={placeholder?.url}\n blurRadius={placeholder?.blur ?? 0}\n progressiveDelivery={progressiveDelivery}\n />\n )\n }\n}\n\ntype PreviewImage = {\n src: string\n loader?: NonNullable<ImageProps[\"loader\"]>\n quality?: ImageProps[\"quality\"]\n unoptimized: boolean\n}\n\nfunction previewImage({\n recipe,\n asset,\n props,\n}: {\n recipe: IrisRecipe\n asset: IrisCatalogAsset\n props: ImageProps\n}): PreviewImage | undefined {\n const preview = recipe.preview\n if (!preview || preview === \"none\") return undefined\n if (preview.url)\n return preview.url === asset.src\n ? undefined\n : { src: preview.url, unoptimized: true }\n\n // Next knows the optimizer's allowed widths (and any custom loader). Build\n // those URLs once, then map each responsive final descriptor to a smaller\n // preview URL. The browser applies the same `sizes` to both images, so a\n // ratio follows the width it actually selects at each viewport and DPR.\n const quality = preview.quality ?? props.quality\n const { props: candidates } = getImageProps({\n ...props,\n quality,\n width: 1,\n height: 1,\n fill: false,\n sizes: \"1px\",\n placeholder: \"empty\",\n overrideSrc: undefined,\n })\n const widths = (candidates.srcSet?.split(\", \") ?? []).flatMap((item) => {\n const match = item.match(/^(.*) (\\d+)w$/)\n return match ? [{ url: match[1], width: Number(match[2]) }] : []\n })\n if (!widths.length) return undefined\n const loader: NonNullable<ImageProps[\"loader\"]> = ({ width }) => {\n const cap =\n preview.ratio === undefined\n ? Math.min(width, preview.maxWidth ?? 640)\n : Math.min(\n Math.floor(width * preview.ratio),\n preview.maxWidth ?? Infinity\n )\n const smaller = widths.filter((candidate) => candidate.width < width)\n const selected =\n smaller.filter((candidate) => candidate.width <= cap).at(-1) ??\n smaller[0] ??\n widths[0]\n return selected.url\n }\n return { src: asset.src, loader, quality, unoptimized: false }\n}\n\nfunction DeliveryImage({\n source,\n recipeName,\n preview,\n loadPreviewFirst,\n previewMinDisplayMs,\n progressiveDelivery,\n style,\n onLoad,\n onError,\n onLoadCapture,\n blurDataURL,\n blurRadius,\n ...props\n}: ImageProps & {\n source: string\n recipeName: string\n preview?: PreviewImage\n loadPreviewFirst: boolean\n previewMinDisplayMs: number\n blurRadius: number\n progressiveDelivery: boolean\n \"data-iris\"?: string\n}) {\n const [loaded, setLoaded] = useState(false)\n const [previewLoaded, setPreviewLoaded] = useState(false)\n const [finalStarted, setFinalStarted] = useState(!loadPreviewFirst)\n // Keep each fallback behind the final image. Browsers can paint an image as\n // soon as it decodes, even before React hydrates and receives its load event.\n // Covering the final with a blur made already-loaded images wait for hydration.\n const layerZIndex = typeof style?.zIndex === \"number\" ? style.zIndex : 0\n const iris = useIrisDebugger()\n const finalRef = useRef<HTMLImageElement | null>(null)\n const previewRef = useRef<HTMLImageElement | null>(null)\n const previewMarked = useRef(false)\n const attachFinal = useCallback((image: HTMLImageElement | null) => {\n finalRef.current = image\n if (image?.complete && image.naturalWidth && !isEmptyImage(image))\n setLoaded(true)\n }, [])\n useEffect(() => {\n let timer: ReturnType<typeof setTimeout> | undefined\n let observer: IntersectionObserver | undefined\n const startFallback = () => {\n timer ??= setTimeout(() => setFinalStarted(true), 3000)\n }\n if (loadPreviewFirst && !finalStarted && previewLoaded) {\n timer = setTimeout(() => setFinalStarted(true), previewMinDisplayMs)\n } else if (loadPreviewFirst && !finalStarted && previewRef.current) {\n if (typeof IntersectionObserver === \"undefined\") startFallback()\n else {\n observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n startFallback()\n observer?.disconnect()\n }\n })\n observer.observe(previewRef.current)\n }\n }\n return () => {\n observer?.disconnect()\n if (timer) clearTimeout(timer)\n }\n }, [loadPreviewFirst, finalStarted, previewLoaded, previewMinDisplayMs])\n const revealPreview = useCallback(\n (image: HTMLImageElement) => {\n if (!image.isConnected || !image.naturalWidth || previewMarked.current)\n return\n previewMarked.current = true\n setPreviewLoaded(true)\n if (finalRef.current) iris?.markPreview(finalRef.current, image)\n },\n [iris]\n )\n const attachPreview = useCallback(\n (image: HTMLImageElement | null) => {\n previewRef.current = image\n if (image?.complete && image.naturalWidth) revealPreview(image)\n },\n [revealPreview]\n )\n useLayoutEffect(() => {\n if (iris && finalRef.current) {\n iris.startTimeline(finalRef.current)\n if (\n finalRef.current.complete &&\n finalRef.current.naturalWidth &&\n !isEmptyImage(finalRef.current)\n )\n iris.markFinal(finalRef.current)\n }\n }, [iris, source])\n useLayoutEffect(() => {\n const image = finalRef.current\n if (\n finalStarted &&\n image?.complete &&\n image.naturalWidth &&\n !isEmptyImage(image)\n ) {\n setLoaded(true)\n iris?.markFinal(image)\n }\n }, [finalStarted, iris])\n const layers = (\n <>\n <Image\n {...props}\n src={finalStarted ? props.src : EMPTY_IMAGE}\n unoptimized={finalStarted ? props.unoptimized : true}\n loader={finalStarted ? props.loader : undefined}\n overrideSrc={finalStarted ? props.overrideSrc : undefined}\n priority={finalStarted ? props.priority : false}\n preload={finalStarted ? props.preload : false}\n fetchPriority={finalStarted ? props.fetchPriority : \"auto\"}\n ref={attachFinal}\n data-iris={props[\"data-iris\"] || props.alt || source}\n data-iris-src={source}\n data-iris-recipe={recipeName}\n data-iris-delivery={progressiveDelivery ? \"progressive\" : undefined}\n placeholder=\"empty\"\n style={{\n ...style,\n ...(preview || blurDataURL ? { zIndex: layerZIndex + 2 } : {}),\n ...(loaded ? { backgroundImage: \"none\" } : {}),\n }}\n onLoadCapture={(event) => {\n if (isEmptyImage(event.currentTarget)) return\n event.currentTarget.style.backgroundImage = \"none\"\n setLoaded(true)\n onLoadCapture?.(event)\n }}\n onLoad={(event) => {\n if (isEmptyImage(event.currentTarget)) return\n setLoaded(true)\n iris?.markFinal(event.currentTarget)\n onLoad?.(event)\n }}\n onError={(event) => {\n if (isEmptyImage(event.currentTarget)) return\n onError?.(event)\n }}\n />\n {preview && !loaded && (\n <Image\n {...props}\n src={preview.src}\n loader={preview.loader}\n overrideSrc={undefined}\n ref={attachPreview}\n unoptimized={preview.unoptimized}\n quality={preview.quality}\n fill\n width={undefined}\n height={undefined}\n alt=\"\"\n aria-hidden=\"true\"\n data-iris={undefined}\n data-slot=\"image-preview\"\n priority={false}\n preload={false}\n fetchPriority=\"auto\"\n placeholder=\"empty\"\n style={{\n ...style,\n pointerEvents: \"none\",\n zIndex: layerZIndex + 1,\n backgroundImage: \"none\",\n }}\n onLoadCapture={(event) => revealPreview(event.currentTarget)}\n onError={() => setFinalStarted(true)}\n />\n )}\n {!loaded && !previewLoaded && blurDataURL && (\n <span\n aria-hidden=\"true\"\n data-slot=\"image-placeholder\"\n data-stage=\"blur\"\n style={{\n position: \"absolute\",\n inset: 0,\n ...style,\n pointerEvents: \"none\",\n zIndex: layerZIndex,\n backgroundImage: `url(${JSON.stringify(blurDataURL)})`,\n backgroundSize:\n style?.objectFit === \"contain\"\n ? \"contain\"\n : style?.objectFit === \"cover\"\n ? \"cover\"\n : \"100% 100%\",\n backgroundPosition: style?.objectPosition ?? \"center\",\n backgroundRepeat: \"no-repeat\",\n filter: `blur(${blurRadius}px)`,\n clipPath: \"inset(0)\",\n }}\n />\n )}\n </>\n )\n // Isolate the layers so their z-index cannot cover captions or controls in\n // the parent. Keep the containing block mounted after load to avoid shifts.\n return preview || blurDataURL ? (\n <span\n data-slot=\"iris-image\"\n style={\n props.fill\n ? { position: \"absolute\", inset: 0, isolation: \"isolate\" }\n : {\n position: \"relative\",\n display: \"inline-grid\",\n maxWidth: \"100%\",\n isolation: \"isolate\",\n }\n }\n >\n {layers}\n </span>\n ) : (\n layers\n )\n}\n","import type { IrisCatalogAsset, IrisRecipe } from \"./policy-types\"\n\nexport function placeholderSettings(input: IrisRecipe[\"placeholder\"]) {\n if (!input || input === \"none\") return null\n const options = input === \"blur\" ? {} : input\n return {\n width: options.width ?? 10,\n quality: options.quality ?? 40,\n blur: options.blur ?? 12,\n }\n}\n\nexport function resolvePlaceholder({\n asset,\n placeholder,\n}: {\n asset: IrisCatalogAsset\n placeholder: IrisRecipe[\"placeholder\"]\n}) {\n const settings = placeholderSettings(placeholder)\n if (!settings) return null\n const url =\n asset.placeholders?.find(\n (item) =>\n item.width === settings.width && item.quality === settings.quality\n )?.url ??\n (settings.width === 10 && settings.quality === 40\n ? asset.placeholder\n : undefined)\n return url ? { url, blur: settings.blur } : null\n}\n"],"mappings":"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","names":[]}