react-native-fast-image 10.1.1 → 10.3.0

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.
Files changed (35) hide show
  1. package/README.md +107 -14
  2. package/android/src/main/java/com/fastimage/FastImageBlur.java +3 -2
  3. package/android/src/main/java/com/fastimage/FastImageThumbHash.java +246 -0
  4. package/android/src/main/java/com/fastimage/FastImageViewConverter.java +1 -1
  5. package/android/src/main/java/com/fastimage/FastImageViewManager.java +20 -5
  6. package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +125 -48
  7. package/dist/index.cjs.js +47 -9
  8. package/dist/index.cjs.js.flow +11 -0
  9. package/dist/index.cjs.web.js +67 -14
  10. package/dist/index.cjs.web.js.flow +11 -0
  11. package/dist/index.d.ts +77 -13
  12. package/dist/index.js +48 -10
  13. package/dist/index.js.flow +11 -0
  14. package/dist/index.web.js +68 -15
  15. package/dist/index.web.js.flow +11 -0
  16. package/dist/placeholders.d.ts +4 -0
  17. package/dist/specs/FastImageViewNativeComponent.d.ts +2 -1
  18. package/dist/thumbhash.cjs.js +241 -0
  19. package/dist/thumbhash.d.ts +12 -0
  20. package/dist/thumbhash.js +240 -0
  21. package/ios/FastImage/FFFBoxBlur.h +7 -0
  22. package/ios/FastImage/FFFBoxBlur.m +29 -0
  23. package/ios/FastImage/FFFThumbHash.h +24 -0
  24. package/ios/FastImage/FFFThumbHash.m +269 -0
  25. package/ios/FastImage/FFFastImageView.h +7 -1
  26. package/ios/FastImage/FFFastImageView.m +93 -46
  27. package/ios/FastImage/FFFastImageViewComponentView.mm +11 -4
  28. package/package.json +4 -2
  29. package/src/index.js.flow +11 -0
  30. package/src/index.tsx +166 -31
  31. package/src/index.web.tsx +166 -33
  32. package/src/placeholders.ts +45 -0
  33. package/src/specs/FastImageViewNativeComponent.ts +8 -2
  34. package/src/thumbhash.ts +363 -0
  35. package/thumbhash/package.json +8 -0
package/dist/index.d.ts CHANGED
@@ -35,6 +35,8 @@ export type Source = {
35
35
  * - `'normal'`: the default.
36
36
  * - `'high'`: e.g. the image the screen is about.
37
37
  *
38
+ * On the web, it's the image's `fetchpriority`.
39
+ *
38
40
  * @default 'normal'
39
41
  */
40
42
  priority?: Priority;
@@ -111,6 +113,32 @@ export type Source = {
111
113
  * @default true
112
114
  */
113
115
  diskCache?: boolean;
116
+ /**
117
+ * A [ThumbHash](https://evanw.github.io/thumbhash/) of the image, shown
118
+ * blurred while the image loads, and if it fails to load. It's a short
119
+ * string you make when the image is uploaded (e.g. with the `thumbhash`
120
+ * package) and send with the image's url.
121
+ *
122
+ * On the web, it needs `import 'react-native-fast-image/thumbhash'` (see
123
+ * [ThumbHashes on the web](#thumbhashes-on-the-web)).
124
+ *
125
+ * @example
126
+ * ```jsx
127
+ * <FastImage source={{ uri: photo.url, thumbhash: photo.thumbhash }} />
128
+ * ```
129
+ */
130
+ thumbhash?: string;
131
+ /**
132
+ * A tiny version of the image (e.g. 8 to 20 px) as a data uri, shown
133
+ * blurred while the image loads, and if it fails to load. On the web it
134
+ * shows without any JavaScript, e.g. in a server-rendered page.
135
+ *
136
+ * @example
137
+ * ```jsx
138
+ * <FastImage source={{ uri: photo.url, blurDataUri: photo.lqip }} />
139
+ * ```
140
+ */
141
+ blurDataUri?: string;
114
142
  /**
115
143
  * With several sources (`source` as an array), the image's width at this
116
144
  * uri, in pixels (times `scale`, if given).
@@ -124,6 +152,8 @@ export type Source = {
124
152
  /** With several sources, the scale `width` and `height` are multiplied by. */
125
153
  scale?: number;
126
154
  };
155
+ /** A `thumbhash` or `blurDataUri` as `placeholder`. */
156
+ export type SourcePlaceholder = Pick<Source, 'thumbhash' | 'blurDataUri'>;
127
157
  /** How the image fades in: see [`transition`](#transition). */
128
158
  export type Transition = {
129
159
  /**
@@ -135,7 +165,7 @@ export type Transition = {
135
165
  /**
136
166
  * Also fades between images: a new `source` replacing an image that's
137
167
  * showing cross-dissolves from it. Otherwise only an image that appears
138
- * over nothing (or over `defaultSource`) fades in, and a new source
168
+ * over nothing (or over a placeholder) fades in, and a new source
139
169
  * replaces the image showing at once, once it has loaded.
140
170
  * @default false
141
171
  */
@@ -303,14 +333,29 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
303
333
  */
304
334
  source?: Source | Source[] | ImageRequireSource;
305
335
  /**
306
- * An asset loaded with `require(...)`, shown while the first image loads,
307
- * and if an image fails to load. When `source` changes, the previous image
308
- * shows while the new one loads instead (see `source` and
309
- * `recyclingKey`).
336
+ * A placeholder shown while the image loads, and if it fails to load:
337
+ *
338
+ * - An image loaded with `require()`: `placeholder={require('./gray.png')}`.
339
+ * - A data uri: `placeholder="data:image/png;base64,…"`.
340
+ * - The name of an image bundled with the app (an iOS asset catalog image
341
+ * or an Android drawable): `placeholder="gray"`.
342
+ * - A [ThumbHash](#sourcethumbhash), shown blurred:
343
+ * `placeholder={{ thumbhash }}`.
344
+ * - A tiny image as a data uri, shown blurred:
345
+ * `placeholder={{ blurDataUri }}`.
346
+ *
347
+ * A source's own `thumbhash` or `blurDataUri` is shown instead, if it has
348
+ * one.
310
349
  *
311
- * On Android, `defaultSource` doesn't show in debug builds: there the dev
312
- * server serves `require()`d images, and `defaultSource` is only loaded
313
- * from the app's resources.
350
+ * Notes:
351
+ *
352
+ * - A url isn't loaded: it's ignored, with a warning in development.
353
+ * - On Android, a `require()`d image doesn't show in debug builds, where
354
+ * the dev server serves it. Release builds show it.
355
+ */
356
+ placeholder?: ImageRequireSource | string | SourcePlaceholder;
357
+ /**
358
+ * @deprecated Use `placeholder`, which does the same.
314
359
  */
315
360
  defaultSource?: ImageRequireSource;
316
361
  /**
@@ -353,8 +398,8 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
353
398
  * `{ right: 10, bottom: '20%' }`. A missing axis is centered.
354
399
  *
355
400
  * Points are dp on Android. An invalid value is ignored (the image is
356
- * centered), with a warning in development. `defaultSource` is placed
357
- * the same way.
401
+ * centered), with a warning in development. Placeholders are placed the
402
+ * same way.
358
403
  *
359
404
  * @default '50% 50%'
360
405
  * @example
@@ -367,7 +412,7 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
367
412
  * For views that get reused for other content, such as rows in FlashList
368
413
  * or recyclerlistview. Set it to something that identifies the content,
369
414
  * e.g. the item's id. When it changes, the image is cleared right away (to
370
- * `defaultSource`, or blank) instead of staying until the new one has
415
+ * `placeholder`, or blank) instead of staying until the new one has
371
416
  * loaded. Unlike changing `key`, this keeps the view, which is what list
372
417
  * recycling is for.
373
418
  *
@@ -409,6 +454,25 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
409
454
  * @default 'auto'
410
455
  */
411
456
  imageRendering?: 'auto' | 'smooth' | 'pixelated';
457
+ /**
458
+ * Whether the image loads as it nears the screen (`'lazy'`) or at once
459
+ * (`'eager'`).
460
+ *
461
+ * @default 'lazy'
462
+ * @platform web
463
+ */
464
+ loading?: 'lazy' | 'eager';
465
+ /**
466
+ * With several sources, the width the image is shown at, as the image's
467
+ * [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#sizes),
468
+ * so the browser loads the size that fits. The default is the width the
469
+ * image is laid out at, which needs `loading="lazy"` (otherwise, or in
470
+ * browsers without it, the window's width).
471
+ *
472
+ * @default 'auto, 100vw'
473
+ * @platform web
474
+ */
475
+ sizes?: string;
412
476
  /**
413
477
  * Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on
414
478
  * the frame it's showing; `false` plays it again from there
@@ -477,7 +541,7 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
477
541
  * blurring a photo behind a sheet). Each change blurs the image again on
478
542
  * the CPU, so don't animate it.
479
543
  *
480
- * - Only the loaded image is blurred, not `defaultSource`.
544
+ * - Only the loaded image is blurred, not a placeholder.
481
545
  * - An animated image (GIF, animated WebP, APNG or AVIF) shows its first frame,
482
546
  * blurred, and doesn't animate.
483
547
  * - With `tintColor`, the blurred image is tinted.
@@ -559,7 +623,7 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
559
623
  style?: StyleProp<ImageStyle>;
560
624
  /**
561
625
  * If supplied, changes the color of all the non-transparent pixels to the
562
- * given color.
626
+ * given color, including the placeholder.
563
627
  */
564
628
  tintColor?: ColorValue;
565
629
  /**
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { Children, forwardRef, memo } from "react";
1
+ import React, { Children, forwardRef, memo, useMemo } from "react";
2
2
  import { Image, Platform, StyleSheet, TurboModuleRegistry, View, codegenNativeComponent } from "react-native";
3
3
  //#region src/constants.ts
4
4
  const resizeMode = {
@@ -233,10 +233,39 @@ var FastImageViewNativeComponent_default = codegenNativeComponent("FastImageView
233
233
  //#region src/specs/NativeFastImageModule.ts
234
234
  var NativeFastImageModule_default = TurboModuleRegistry.get("FastImageModule");
235
235
  //#endregion
236
+ //#region src/placeholders.ts
237
+ function isImageName(value) {
238
+ return !/^[a-z][a-z\d+.-]*:/i.test(value) && !value.startsWith("/");
239
+ }
240
+ function hasSourcePlaceholder(value) {
241
+ return !!value && typeof value === "object" && ("thumbhash" in value || "blurDataUri" in value);
242
+ }
243
+ function dataUri(value) {
244
+ if (typeof value !== "string" || value === "") return void 0;
245
+ if (/^data:/i.test(value)) return value;
246
+ if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`placeholder\` and \`blurDataUri\` don't load urls: "${value.slice(0, 60)}" is ignored. Use a data uri (data:image/…), or for \`placeholder\`, a require()d image or the name of an image bundled with the app.`, "warn");
247
+ }
248
+ //#endregion
236
249
  //#region src/index.tsx
237
- const resolveDefaultSource = (defaultSource) => {
238
- if (!defaultSource) return null;
239
- const resolved = Image.resolveAssetSource(defaultSource);
250
+ function withoutPlaceholders(source) {
251
+ if (!hasSourcePlaceholder(source)) return source;
252
+ const { thumbhash: _thumbhash, blurDataUri: _blurDataUri, ...rest } = source;
253
+ return rest;
254
+ }
255
+ function sourcePlaceholder(source) {
256
+ if (!source || typeof source !== "object") return "";
257
+ const { thumbhash, blurDataUri } = source;
258
+ return thumbhash || dataUri(blurDataUri) || "";
259
+ }
260
+ const resolvePlaceholder = (placeholder) => {
261
+ if (typeof placeholder === "string") {
262
+ if (isImageName(placeholder)) return Platform.OS === "android" ? placeholder : { uri: placeholder };
263
+ const uri = dataUri(placeholder);
264
+ if (!uri) return null;
265
+ return Platform.OS === "android" ? uri : { uri };
266
+ }
267
+ if (!placeholder) return null;
268
+ const resolved = Image.resolveAssetSource(placeholder);
240
269
  if (!resolved) return null;
241
270
  return Platform.OS === "android" ? resolved.uri : resolved;
242
271
  };
@@ -291,7 +320,7 @@ function builtInTransition() {
291
320
  return resolveTransition(Platform.OS === "android", NO_TRANSITION);
292
321
  }
293
322
  let defaultTransition;
294
- function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, forwardedRef, ...viewProps }) {
323
+ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, loading: _loading, sizes: _sizes, forwardedRef, ...viewProps }) {
295
324
  var _props$recyclingKey, _defaultTransition;
296
325
  const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
297
326
  if (__DEV__ && fallback !== void 0) warnOnce("fallback", "react-native-fast-image: `fallback` was removed in 10.0, and does nothing: images always load natively. Remove it, or use React Native's Image for those images.", "warn");
@@ -299,10 +328,18 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
299
328
  const resolvedTintColor = tintColor != null ? tintColor : fromStyle(style, "tintColor");
300
329
  const fit = resolveObjectFit(objectFit, style);
301
330
  const position = objectPositionOrCentered(objectPosition);
302
- const sources = Array.isArray(source) ? source.length === 1 ? void 0 : source : void 0;
303
- const single = Array.isArray(source) ? source[0] : source;
304
- const resolvedSource = sources ? void 0 : Image.resolveAssetSource(single);
305
- const resolvedDefaultSource = resolveDefaultSource(defaultSource);
331
+ const shared = hasSourcePlaceholder(placeholder) ? sourcePlaceholder(placeholder) : "";
332
+ const { resolvedSource, sources, sourcePlaceholders } = useMemo(() => {
333
+ const sources = Array.isArray(source) ? source.length === 1 ? void 0 : source : void 0;
334
+ const single = Array.isArray(source) ? source[0] : source;
335
+ const placeholders = (sources !== null && sources !== void 0 ? sources : [single]).map((entry) => sourcePlaceholder(entry) || shared);
336
+ return {
337
+ resolvedSource: sources ? void 0 : Image.resolveAssetSource(withoutPlaceholders(single)),
338
+ sources: (sources === null || sources === void 0 ? void 0 : sources.some(hasSourcePlaceholder)) ? sources.map(withoutPlaceholders) : sources,
339
+ sourcePlaceholders: placeholders.some(Boolean) ? placeholders : void 0
340
+ };
341
+ }, [source, shared]);
342
+ const resolvedPlaceholder = resolvePlaceholder(hasSourcePlaceholder(placeholder) ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
306
343
  return /* @__PURE__ */ React.createElement(FastImageViewNativeComponent_default, {
307
344
  ...props,
308
345
  ref: forwardedRef,
@@ -312,7 +349,8 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
312
349
  ...resolveTransition(transition, (_defaultTransition = defaultTransition) !== null && _defaultTransition !== void 0 ? _defaultTransition : builtInTransition()),
313
350
  source: resolvedSource,
314
351
  sources,
315
- defaultSource: resolvedDefaultSource,
352
+ sourcePlaceholders,
353
+ placeholder: resolvedPlaceholder,
316
354
  onFastImageLoadStart: onLoadStart,
317
355
  onFastImageProgress: onProgress,
318
356
  handledEvents: handledEvents({
@@ -110,6 +110,11 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
110
110
  export type PreloadFn = (
111
111
  sources: Array<FastImageSource>,
112
112
  ) => Promise<Array<PreloadResult>>
113
+ export type SourcePlaceholder = {
114
+ thumbhash?: string,
115
+ blurDataUri?: string,
116
+ }
117
+
113
118
  export type FastImageSource = {
114
119
  uri?: string,
115
120
  headers?: Object,
@@ -118,6 +123,8 @@ export type FastImageSource = {
118
123
  cacheKey?: string,
119
124
  memoryCache?: boolean,
120
125
  diskCache?: boolean,
126
+ thumbhash?: string,
127
+ blurDataUri?: string,
121
128
  width?: number,
122
129
  height?: number,
123
130
  scale?: number,
@@ -132,6 +139,8 @@ export type FastImageProps = $ReadOnly<{|
132
139
  onProgress?: ?(event: OnProgressEvent) => void,
133
140
 
134
141
  source?: ?(FastImageSource | Array<FastImageSource> | number),
142
+ placeholder?: ?(number | string | SourcePlaceholder),
143
+ // Deprecated: use placeholder, which is the same.
135
144
  defaultSource?: ?number,
136
145
 
137
146
  tintColor?: number | string,
@@ -140,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
140
149
  recyclingKey?: ?string,
141
150
  loop?: ?(boolean | number),
142
151
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
+ loading?: ?('lazy' | 'eager'),
153
+ sizes?: ?string,
143
154
  paused?: ?boolean,
144
155
  transition?: ?(boolean | number | Transition),
145
156
  downsample?: ?boolean,
package/dist/index.web.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { Children, forwardRef, memo, useEffect, useLayoutEffect, useRef, useState } from "react";
1
+ import React, { Children, forwardRef, memo, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
2
2
  import { Image, PixelRatio, StyleSheet, View } from "react-native";
3
3
  import { getAssetByID } from "react-native-web/dist/modules/AssetRegistry";
4
4
  //#region src/constants.ts
@@ -240,17 +240,24 @@ function cssPosition({ x, y }) {
240
240
  return `${axis(x)} ${axis(y)}`;
241
241
  }
242
242
  //#endregion
243
+ //#region src/placeholders.ts
244
+ function hasSourcePlaceholder(value) {
245
+ return !!value && typeof value === "object" && ("thumbhash" in value || "blurDataUri" in value);
246
+ }
247
+ function dataUri(value) {
248
+ if (typeof value !== "string" || value === "") return void 0;
249
+ if (/^data:/i.test(value)) return value;
250
+ if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`placeholder\` and \`blurDataUri\` don't load urls: "${value.slice(0, 60)}" is ignored. Use a data uri (data:image/…), or for \`placeholder\`, a require()d image or the name of an image bundled with the app.`, "warn");
251
+ }
252
+ //#endregion
243
253
  //#region src/index.web.tsx
244
- var _useId;
245
254
  const notSupported = {
246
255
  ok: false,
247
256
  error: "Not supported on the web"
248
257
  };
249
- let ids = 0;
250
- const useCounterId = () => useState(() => `${++ids}`)[0];
251
- const useSvgId = (_useId = React.useId) !== null && _useId !== void 0 ? _useId : useCounterId;
252
258
  const useClientLayoutEffect = globalThis.document === void 0 ? useEffect : useLayoutEffect;
253
- function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
259
+ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loading, sizes, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
260
+ var _resolveUri;
254
261
  if (typeof __DEV__ !== "undefined" && __DEV__) warnIfChildren(children);
255
262
  const { accessibilityLabel, "aria-label": ariaLabel, resizeMode: _resizeMode, ...viewProps } = props;
256
263
  const label = ariaLabel || accessibilityLabel || void 0;
@@ -259,10 +266,15 @@ function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadSta
259
266
  const src = sized ? sized.src : resolveUri(source);
260
267
  const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
261
268
  const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
262
- const defaultUri = resolveUri(defaultSource);
269
+ const priority = sourcePriority(source);
270
+ const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
271
+ const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
272
+ const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
263
273
  const [result, setResult] = useState();
264
274
  const ok = result && result.key === key ? result.ok : void 0;
265
- const tintId = `fast-image-tint-${useSvgId().replace(/[^\w-]/g, "")}`;
275
+ const svgId = useId().replace(/[^\w-]/g, "");
276
+ const tintId = `fast-image-tint-${svgId}`;
277
+ const blurId = `fast-image-blur-${svgId}`;
266
278
  const fit = resolveObjectFit(objectFit, style);
267
279
  const position = cssPosition(objectPositionOrCentered(objectPosition));
268
280
  const filter = [blurRadius ? `blur(${blurRadius}px)` : "", tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") || void 0;
@@ -280,15 +292,19 @@ function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadSta
280
292
  }, defaultUri && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
281
293
  key: `default ${defaultUri}`,
282
294
  src: defaultUri,
283
- fit,
295
+ loading,
296
+ priority,
297
+ fit: blurUri && (fit === "scale-down" || fit === "none") ? "contain" : fit,
284
298
  position,
285
- filter,
299
+ filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
286
300
  alt: key ? "" : label !== null && label !== void 0 ? label : ""
287
301
  }) : null, key && src ? /* @__PURE__ */ React.createElement(Picture, {
288
302
  key: several ? "sizes" : key,
289
303
  src,
290
304
  srcSet,
291
- lazy: !!several,
305
+ sizes,
306
+ loading,
307
+ priority,
292
308
  fit,
293
309
  position,
294
310
  filter,
@@ -325,7 +341,29 @@ function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadSta
325
341
  }) : null, tintColor ? /* @__PURE__ */ React.createElement(TintFilter, {
326
342
  id: tintId,
327
343
  color: tintColor
328
- }) : null);
344
+ }) : null, blurUri && showDefault ? /* @__PURE__ */ React.createElement(PreviewBlur, { id: blurId }) : null);
345
+ }
346
+ function thumbhashUri(hash) {
347
+ if (!hash) return void 0;
348
+ const decode = globalThis[Symbol.for("react-native-fast-image/thumbhash")];
349
+ if (decode) return decode(hash);
350
+ if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("thumbhash-web", "react-native-fast-image: on the web, a `thumbhash` shows only with `import 'react-native-fast-image/thumbhash'` (it decodes them). Or make it a `blurDataUri` before it reaches the browser (e.g. on your server) with that module's thumbHashToBlurDataUri, which needs no JavaScript in the browser.", "warn");
351
+ }
352
+ function sourcePriority(source) {
353
+ const values = (Array.isArray(source) ? source : [source]).map((entry) => entry && typeof entry === "object" ? entry.priority : void 0);
354
+ return [
355
+ "high",
356
+ "normal",
357
+ "low"
358
+ ].find((priority) => values.includes(priority));
359
+ }
360
+ function shared(source, key, preview) {
361
+ const values = (Array.isArray(source) ? source : [source]).map((entry) => {
362
+ var _ref;
363
+ const own = entry && typeof entry === "object" ? entry : void 0;
364
+ return (_ref = (own === null || own === void 0 ? void 0 : own.thumbhash) || dataUri(own === null || own === void 0 ? void 0 : own.blurDataUri) ? own : preview) === null || _ref === void 0 ? void 0 : _ref[key];
365
+ });
366
+ return values.every((value) => value && value === values[0]) ? values[0] : void 0;
329
367
  }
330
368
  function resolveUri(source) {
331
369
  let uri;
@@ -373,7 +411,7 @@ const fill = {
373
411
  width: "100%",
374
412
  height: "100%"
375
413
  };
376
- function Picture({ src, srcSet, lazy, fit, position, filter, hidden, alt, onLoad, onError }) {
414
+ function Picture({ src, srcSet, sizes, loading, priority, fit, position, filter, hidden, alt, onLoad, onError }) {
377
415
  const image = useRef(null);
378
416
  const mounted = useRef(true);
379
417
  const handled = useRef(void 0);
@@ -410,8 +448,9 @@ function Picture({ src, srcSet, lazy, fit, position, filter, hidden, alt, onLoad
410
448
  ref: image,
411
449
  src,
412
450
  srcSet,
413
- sizes: srcSet ? "auto, 100vw" : void 0,
414
- loading: lazy ? "lazy" : void 0,
451
+ sizes: srcSet ? sizes !== null && sizes !== void 0 ? sizes : "auto, 100vw" : void 0,
452
+ loading: loading === "eager" ? void 0 : "lazy",
453
+ fetchPriority: priority === "high" || priority === "low" ? priority : void 0,
415
454
  alt,
416
455
  draggable: false,
417
456
  style: {
@@ -437,6 +476,20 @@ function TintFilter({ id, color }) {
437
476
  operator: "in"
438
477
  }))));
439
478
  }
479
+ function PreviewBlur({ id }) {
480
+ return /* @__PURE__ */ React.createElement("svg", { style: tintStyle }, /* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("filter", {
481
+ id,
482
+ x: "0",
483
+ y: "0",
484
+ width: "1",
485
+ height: "1",
486
+ primitiveUnits: "objectBoundingBox",
487
+ colorInterpolationFilters: "sRGB"
488
+ }, /* @__PURE__ */ React.createElement("feGaussianBlur", { stdDeviation: "0.06" }), /* @__PURE__ */ React.createElement("feComponentTransfer", null, /* @__PURE__ */ React.createElement("feFuncA", {
489
+ type: "table",
490
+ tableValues: "0 1 1 1 1"
491
+ })))));
492
+ }
440
493
  const tintStyle = {
441
494
  position: "absolute",
442
495
  width: 0,
@@ -110,6 +110,11 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
110
110
  export type PreloadFn = (
111
111
  sources: Array<FastImageSource>,
112
112
  ) => Promise<Array<PreloadResult>>
113
+ export type SourcePlaceholder = {
114
+ thumbhash?: string,
115
+ blurDataUri?: string,
116
+ }
117
+
113
118
  export type FastImageSource = {
114
119
  uri?: string,
115
120
  headers?: Object,
@@ -118,6 +123,8 @@ export type FastImageSource = {
118
123
  cacheKey?: string,
119
124
  memoryCache?: boolean,
120
125
  diskCache?: boolean,
126
+ thumbhash?: string,
127
+ blurDataUri?: string,
121
128
  width?: number,
122
129
  height?: number,
123
130
  scale?: number,
@@ -132,6 +139,8 @@ export type FastImageProps = $ReadOnly<{|
132
139
  onProgress?: ?(event: OnProgressEvent) => void,
133
140
 
134
141
  source?: ?(FastImageSource | Array<FastImageSource> | number),
142
+ placeholder?: ?(number | string | SourcePlaceholder),
143
+ // Deprecated: use placeholder, which is the same.
135
144
  defaultSource?: ?number,
136
145
 
137
146
  tintColor?: number | string,
@@ -140,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
140
149
  recyclingKey?: ?string,
141
150
  loop?: ?(boolean | number),
142
151
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
+ loading?: ?('lazy' | 'eager'),
153
+ sizes?: ?string,
143
154
  paused?: ?boolean,
144
155
  transition?: ?(boolean | number | Transition),
145
156
  downsample?: ?boolean,
@@ -0,0 +1,4 @@
1
+ import type { SourcePlaceholder } from './index';
2
+ export declare function isImageName(value: string): boolean;
3
+ export declare function hasSourcePlaceholder(value: unknown): value is SourcePlaceholder;
4
+ export declare function dataUri(value: unknown): string | undefined;
@@ -21,7 +21,8 @@ type LoadEndEvent = Readonly<{
21
21
  export interface NativeProps extends ViewProps {
22
22
  source?: CodegenTypes.UnsafeMixed;
23
23
  sources?: CodegenTypes.UnsafeMixed;
24
- defaultSource?: CodegenTypes.UnsafeMixed;
24
+ placeholder?: CodegenTypes.UnsafeMixed;
25
+ sourcePlaceholders?: ReadonlyArray<string>;
25
26
  objectFit?: CodegenTypes.WithDefault<string, 'cover'>;
26
27
  objectPositionX?: CodegenTypes.WithDefault<CodegenTypes.Float, 0.5>;
27
28
  objectPositionXOffset?: CodegenTypes.Float;