react-native-fast-image 10.3.0 → 10.5.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.
package/dist/index.js CHANGED
@@ -234,8 +234,14 @@ var FastImageViewNativeComponent_default = codegenNativeComponent("FastImageView
234
234
  var NativeFastImageModule_default = TurboModuleRegistry.get("FastImageModule");
235
235
  //#endregion
236
236
  //#region src/placeholders.ts
237
+ const SCHEME = /^[a-z][a-z\d+.-]*:/i;
237
238
  function isImageName(value) {
238
- return !/^[a-z][a-z\d+.-]*:/i.test(value) && !value.startsWith("/");
239
+ return !SCHEME.test(value) && !value.startsWith("/");
240
+ }
241
+ function placeholderSource(value) {
242
+ const uri = typeof value === "string" ? value : value && typeof value === "object" ? value.uri : void 0;
243
+ if (typeof uri !== "string" || !/^https?:/i.test(uri)) return void 0;
244
+ return typeof value === "string" ? { uri } : value;
239
245
  }
240
246
  function hasSourcePlaceholder(value) {
241
247
  return !!value && typeof value === "object" && ("thumbhash" in value || "blurDataUri" in value);
@@ -243,7 +249,7 @@ function hasSourcePlaceholder(value) {
243
249
  function dataUri(value) {
244
250
  if (typeof value !== "string" || value === "") return void 0;
245
251
  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");
252
+ if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`blurDataUri\` takes a data uri (data:image/…): "${value.slice(0, 60)}" is ignored. For a placeholder loaded from a url, use \`placeholder\`.`, "warn");
247
253
  }
248
254
  //#endregion
249
255
  //#region src/index.tsx
@@ -321,7 +327,7 @@ function builtInTransition() {
321
327
  }
322
328
  let defaultTransition;
323
329
  function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, loading: _loading, sizes: _sizes, forwardedRef, ...viewProps }) {
324
- var _props$recyclingKey, _defaultTransition;
330
+ var _placeholderSource, _props$recyclingKey, _defaultTransition;
325
331
  const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
326
332
  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");
327
333
  if (__DEV__) warnIfChildren(children);
@@ -339,7 +345,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
339
345
  sourcePlaceholders: placeholders.some(Boolean) ? placeholders : void 0
340
346
  };
341
347
  }, [source, shared]);
342
- const resolvedPlaceholder = resolvePlaceholder(hasSourcePlaceholder(placeholder) ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
348
+ const resolvedPlaceholder = (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(hasSourcePlaceholder(placeholder) ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
343
349
  return /* @__PURE__ */ React.createElement(FastImageViewNativeComponent_default, {
344
350
  ...props,
345
351
  ref: forwardedRef,
@@ -110,6 +110,13 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
110
110
  export type PreloadFn = (
111
111
  sources: Array<FastImageSource>,
112
112
  ) => Promise<Array<PreloadResult>>
113
+ export type PlaceholderSource = {
114
+ uri?: string,
115
+ headers?: Object,
116
+ cacheKey?: string,
117
+ cache?: CacheControls,
118
+ }
119
+
113
120
  export type SourcePlaceholder = {
114
121
  thumbhash?: string,
115
122
  blurDataUri?: string,
@@ -139,7 +146,7 @@ export type FastImageProps = $ReadOnly<{|
139
146
  onProgress?: ?(event: OnProgressEvent) => void,
140
147
 
141
148
  source?: ?(FastImageSource | Array<FastImageSource> | number),
142
- placeholder?: ?(number | string | SourcePlaceholder),
149
+ placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
143
150
  // Deprecated: use placeholder, which is the same.
144
151
  defaultSource?: ?number,
145
152
 
@@ -151,6 +158,7 @@ export type FastImageProps = $ReadOnly<{|
151
158
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
159
  loading?: ?('lazy' | 'eager'),
153
160
  sizes?: ?string,
161
+ dynamicRangeLimit?: ?('standard' | 'constrained' | 'no-limit'),
154
162
  paused?: ?boolean,
155
163
  transition?: ?(boolean | number | Transition),
156
164
  downsample?: ?boolean,
package/dist/index.web.js CHANGED
@@ -241,13 +241,18 @@ function cssPosition({ x, y }) {
241
241
  }
242
242
  //#endregion
243
243
  //#region src/placeholders.ts
244
+ function placeholderSource(value) {
245
+ const uri = typeof value === "string" ? value : value && typeof value === "object" ? value.uri : void 0;
246
+ if (typeof uri !== "string" || !/^https?:/i.test(uri)) return void 0;
247
+ return typeof value === "string" ? { uri } : value;
248
+ }
244
249
  function hasSourcePlaceholder(value) {
245
250
  return !!value && typeof value === "object" && ("thumbhash" in value || "blurDataUri" in value);
246
251
  }
247
252
  function dataUri(value) {
248
253
  if (typeof value !== "string" || value === "") return void 0;
249
254
  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");
255
+ if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`blurDataUri\` takes a data uri (data:image/…): "${value.slice(0, 60)}" is ignored. For a placeholder loaded from a url, use \`placeholder\`.`, "warn");
251
256
  }
252
257
  //#endregion
253
258
  //#region src/index.web.tsx
@@ -256,8 +261,8 @@ const notSupported = {
256
261
  error: "Not supported on the web"
257
262
  };
258
263
  const useClientLayoutEffect = globalThis.document === void 0 ? useEffect : useLayoutEffect;
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;
264
+ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loading, sizes, dynamicRangeLimit, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
265
+ var _resolveUri, _placeholderSource;
261
266
  if (typeof __DEV__ !== "undefined" && __DEV__) warnIfChildren(children);
262
267
  const { accessibilityLabel, "aria-label": ariaLabel, resizeMode: _resizeMode, ...viewProps } = props;
263
268
  const label = ariaLabel || accessibilityLabel || void 0;
@@ -269,7 +274,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
269
274
  const priority = sourcePriority(source);
270
275
  const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
271
276
  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);
277
+ const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
278
+ const [failedPlaceholder, setFailedPlaceholder] = useState();
279
+ const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview || urlPlaceholder ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
273
280
  const [result, setResult] = useState();
274
281
  const ok = result && result.key === key ? result.ok : void 0;
275
282
  const svgId = useId().replace(/[^\w-]/g, "");
@@ -278,7 +285,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
278
285
  const fit = resolveObjectFit(objectFit, style);
279
286
  const position = cssPosition(objectPositionOrCentered(objectPosition));
280
287
  const filter = [blurRadius ? `blur(${blurRadius}px)` : "", tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") || void 0;
281
- const showDefault = !!defaultUri && ok !== true;
288
+ const showDefault = ok !== true;
282
289
  useClientLayoutEffect(() => {
283
290
  if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
284
291
  }, [key]);
@@ -294,10 +301,23 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
294
301
  src: defaultUri,
295
302
  loading,
296
303
  priority,
304
+ dynamicRangeLimit,
297
305
  fit: blurUri && (fit === "scale-down" || fit === "none") ? "contain" : fit,
298
306
  position,
299
307
  filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
300
308
  alt: key ? "" : label !== null && label !== void 0 ? label : ""
309
+ }) : null, urlPlaceholder && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
310
+ key: `placeholder ${urlPlaceholder}`,
311
+ src: urlPlaceholder,
312
+ loading,
313
+ priority: "high",
314
+ dynamicRangeLimit,
315
+ fit: fit === "scale-down" || fit === "none" ? "contain" : fit,
316
+ position,
317
+ filter: tintColor ? `url(#${tintId})` : void 0,
318
+ hidden: failedPlaceholder === urlPlaceholder,
319
+ onError: () => setFailedPlaceholder(urlPlaceholder),
320
+ alt: ""
301
321
  }) : null, key && src ? /* @__PURE__ */ React.createElement(Picture, {
302
322
  key: several ? "sizes" : key,
303
323
  src,
@@ -305,6 +325,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
305
325
  sizes,
306
326
  loading,
307
327
  priority,
328
+ dynamicRangeLimit,
308
329
  fit,
309
330
  position,
310
331
  filter,
@@ -411,7 +432,7 @@ const fill = {
411
432
  width: "100%",
412
433
  height: "100%"
413
434
  };
414
- function Picture({ src, srcSet, sizes, loading, priority, fit, position, filter, hidden, alt, onLoad, onError }) {
435
+ function Picture({ src, srcSet, sizes, loading, priority, dynamicRangeLimit, fit, position, filter, hidden, alt, onLoad, onError }) {
415
436
  const image = useRef(null);
416
437
  const mounted = useRef(true);
417
438
  const handled = useRef(void 0);
@@ -458,7 +479,8 @@ function Picture({ src, srcSet, sizes, loading, priority, fit, position, filter,
458
479
  objectFit: fit,
459
480
  objectPosition: position,
460
481
  filter,
461
- opacity: hidden ? 0 : void 0
482
+ opacity: hidden ? 0 : void 0,
483
+ dynamicRangeLimit
462
484
  },
463
485
  onLoad: (event) => loaded(event.currentTarget),
464
486
  onError: (event) => {
@@ -110,6 +110,13 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
110
110
  export type PreloadFn = (
111
111
  sources: Array<FastImageSource>,
112
112
  ) => Promise<Array<PreloadResult>>
113
+ export type PlaceholderSource = {
114
+ uri?: string,
115
+ headers?: Object,
116
+ cacheKey?: string,
117
+ cache?: CacheControls,
118
+ }
119
+
113
120
  export type SourcePlaceholder = {
114
121
  thumbhash?: string,
115
122
  blurDataUri?: string,
@@ -139,7 +146,7 @@ export type FastImageProps = $ReadOnly<{|
139
146
  onProgress?: ?(event: OnProgressEvent) => void,
140
147
 
141
148
  source?: ?(FastImageSource | Array<FastImageSource> | number),
142
- placeholder?: ?(number | string | SourcePlaceholder),
149
+ placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
143
150
  // Deprecated: use placeholder, which is the same.
144
151
  defaultSource?: ?number,
145
152
 
@@ -151,6 +158,7 @@ export type FastImageProps = $ReadOnly<{|
151
158
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
159
  loading?: ?('lazy' | 'eager'),
153
160
  sizes?: ?string,
161
+ dynamicRangeLimit?: ?('standard' | 'constrained' | 'no-limit'),
154
162
  paused?: ?boolean,
155
163
  transition?: ?(boolean | number | Transition),
156
164
  downsample?: ?boolean,
@@ -1,4 +1,5 @@
1
- import type { SourcePlaceholder } from './index';
1
+ import type { PlaceholderSource, SourcePlaceholder } from './index';
2
2
  export declare function isImageName(value: string): boolean;
3
+ export declare function placeholderSource(value: unknown): PlaceholderSource | undefined;
3
4
  export declare function hasSourcePlaceholder(value: unknown): value is SourcePlaceholder;
4
5
  export declare function dataUri(value: unknown): string | undefined;
@@ -37,6 +37,7 @@ export interface NativeProps extends ViewProps {
37
37
  transitionBetweenImages?: boolean;
38
38
  transitionSkipOnCacheHit?: string;
39
39
  downsample?: CodegenTypes.WithDefault<boolean, true>;
40
+ dynamicRangeLimit?: string;
40
41
  blurRadius?: CodegenTypes.Float;
41
42
  handledEvents?: CodegenTypes.Int32;
42
43
  onFastImageLoadStart?: CodegenTypes.DirectEventHandler<LoadStartEvent>;
@@ -25,12 +25,15 @@
25
25
  // loaded this way was decoded from, for onLoad, or zero for other images.
26
26
  + (CGSize) sourceSizeOfImage: (UIImage*)image;
27
27
 
28
- // SDWebImage puts an image it decoded smaller from the disk cache, for a view
29
- // that downsamples, into the memory cache under the full-size image's key too
30
- // (SDWebImageManager writes what its original cache query finds back there),
31
- // where a full-size load (a view that doesn't downsample, or a preload) would
32
- // find it: a blurry image, or a preload's size. Drops it there, so that load
33
- // decodes the file from the disk cache. Call it before a full-size load.
34
- + (void) forgetSmallerFullSizeImageForURL: (NSURL*)url context: (SDWebImageContext*)context;
28
+ // Drops the memory cache's image for a load (call it before the load) when
29
+ // the load can't use it, so it decodes the file from the disk cache:
30
+ // - for a full-size load (a view that doesn't downsample, or a preload), a
31
+ // smaller image: SDWebImage puts an image it decoded smaller from the disk
32
+ // cache, for a view that downsamples, under the full-size image's key too
33
+ // (SDWebImageManager writes what its original cache query finds back
34
+ // there), which would show blurry, or be a preload's size.
35
+ // - one decoded the other way, to HDR or SDR (dynamicRangeLimit), since
36
+ // SDWebImage's memory cache key is the same for both.
37
+ + (void) forgetUnusableImageForURL: (NSURL*)url context: (SDWebImageContext*)context;
35
38
 
36
39
  @end
@@ -77,7 +77,7 @@ static CGSize FFFPixelSize(NSData* data) {
77
77
  return [sourceSize isKindOfClass: [FFFSourceSize class]] ? ((FFFSourceSize*) sourceSize).size : CGSizeZero;
78
78
  }
79
79
 
80
- + (void) forgetSmallerFullSizeImageForURL: (NSURL*)url context: (SDWebImageContext*)context {
80
+ + (void) forgetUnusableImageForURL: (NSURL*)url context: (SDWebImageContext*)context {
81
81
  SDWebImageManager* manager = [SDWebImageManager sharedManager];
82
82
  if (!url || ![manager.imageCache isKindOfClass: [SDImageCache class]] || ![manager respondsToSelector: @selector(cacheKeyForURL:context:)]) {
83
83
  return;
@@ -85,12 +85,34 @@ static CGSize FFFPixelSize(NSData* data) {
85
85
  SDImageCache* cache = (SDImageCache*) manager.imageCache;
86
86
  NSString* key = [manager cacheKeyForURL: url context: context];
87
87
  UIImage* image = [cache imageFromMemoryCacheForKey: key];
88
- CGSize fullSize = [self sourceSizeOfImage: image];
89
- if (image && fullSize.width > 0 && fullSize.height > 0 && !CGSizeEqualToSize(image.size, fullSize)) {
88
+ if (image && ([self isSmallerFullSizeImage: image context: context] || [self isOtherDynamicRange: image context: context])) {
90
89
  [cache removeImageFromMemoryForKey: key];
91
90
  }
92
91
  }
93
92
 
93
+ // A smaller image under a full-size load's key.
94
+ + (BOOL) isSmallerFullSizeImage: (UIImage*)image context: (SDWebImageContext*)context {
95
+ if (context[SDWebImageContextImageThumbnailPixelSize]) {
96
+ return NO;
97
+ }
98
+ CGSize fullSize = [self sourceSizeOfImage: image];
99
+ return fullSize.width > 0 && fullSize.height > 0 && !CGSizeEqualToSize(image.size, fullSize);
100
+ }
101
+
102
+ // An image decoded to SDR for an HDR load, or to HDR for an SDR load. Only
103
+ // images SDWebImage decoded say how: not a photo library's, or an app's own
104
+ // loader's, which are kept. One decoded to HDR that came out SDR (most images
105
+ // have no HDR) is the same as an SDR decode, and an HDR file decoded without
106
+ // asking is HDR either way (e.g. an HDR10 HEIC on iOS 15 and 16).
107
+ + (BOOL) isOtherDynamicRange: (UIImage*)image context: (SDWebImageContext*)context {
108
+ if (!image.sd_decodeOptions) {
109
+ return NO;
110
+ }
111
+ BOOL wantsHDR = [context[SDWebImageContextImageDecodeToHDR] boolValue];
112
+ BOOL decodedToHDR = [image.sd_decodeOptions[SDImageCoderDecodeToHDR] boolValue];
113
+ return wantsHDR ? !decodedToHDR : decodedToHDR && image.sd_isHighDynamicRange;
114
+ }
115
+
94
116
  - (instancetype) initWithData: (NSData*)data scale: (CGFloat)scale options: (SDImageCoderOptions*)options {
95
117
  // An SVG isn't decoded smaller: it's a vector image, drawn at any size.
96
118
  // This class can't decode it, and SDWebImage would then give it to the
@@ -305,7 +305,7 @@ static NSDictionary *FFFFile(NSString *path)
305
305
  SDWebImageOptions options = FFFPreloadOptions(source);
306
306
  SDWebImageMutableContext *context = FFFPreloadContext(source);
307
307
  // It looks in the memory cache first, for the full-size image.
308
- [FFFDownsampledImage forgetSmallerFullSizeImageForURL:source.url context:context];
308
+ [FFFDownsampledImage forgetUnusableImageForURL:source.url context:context];
309
309
  if (!source.memoryCache) {
310
310
  // Not decoded (only its header is read, for the size), and not
311
311
  // kept in memory, also when it comes from the disk cache.
@@ -34,6 +34,10 @@ typedef NS_ENUM(NSInteger, FFFObjectFit) {
34
34
  @property (nonatomic, copy) NSArray<FFFastImageSource *> *sources;
35
35
  // The `placeholder` prop.
36
36
  @property (nonatomic, strong) UIImage *placeholder;
37
+ // `placeholder` as a url (a source): loaded apart from the
38
+ // image, at a high priority (or only from the cache, with cache cacheOnly),
39
+ // it shows over the other placeholders once it has.
40
+ @property (nonatomic, strong) FFFastImageSource *placeholderSource;
37
41
  // The `sourcePlaceholders` prop, by the source's index (an empty string for
38
42
  // none; one for a single source): the picked source's ThumbHash or
39
43
  // blurDataUri (a data uri) shows instead of placeholder. JS sends them apart
@@ -59,6 +63,10 @@ typedef NS_ENUM(NSInteger, FFFObjectFit) {
59
63
  @property (nonatomic, copy) NSString *transitionSkipOnCacheHit;
60
64
  // Decodes images at about the view's size instead of at full size.
61
65
  @property (nonatomic, assign) BOOL downsample;
66
+ // The dynamicRangeLimit prop: standard, constrained or no-limit (nil when
67
+ // not set). With constrained and no-limit, HDR images are decoded to HDR and
68
+ // shown in HDR (iOS and tvOS 17+).
69
+ @property (nonatomic, copy) NSString *dynamicRangeLimit;
62
70
  // Blurs the loaded image by this radius, in points (0 is no blur).
63
71
  @property (nonatomic, assign) CGFloat blurRadius;
64
72
  // objectPosition, as CSS computes it: on each axis the image's left or top