react-native-fast-image 8.21.0 → 8.23.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/README.md CHANGED
@@ -212,6 +212,27 @@ Pauses an animated image (GIF, and animated WebP on iOS) on the frame it's showi
212
212
 
213
213
  ---
214
214
 
215
+ ### `transition?: boolean | number | Transition`
216
+
217
+ Fades the image in when it loads. Off by default. `true` uses the platform's usual fade, a number is the duration in milliseconds, or pass an object:
218
+
219
+ - `duration`: milliseconds; 0 is no fade. Defaults to the platform's usual length: 300 ms on Android, 250 ms on iOS.
220
+ - `betweenImages` (default `false`): also fade between images. By default an image fades in when it appears over nothing (or over `defaultSource`), and a new `source` replaces the image that's showing at once, once it has loaded. With `true`, the new image cross-dissolves from the one showing (for a gallery or an avatar that changes, say).
221
+ - `skipOnCacheHit`: which images show at once, without the fade:
222
+ - `'memory'` (default): images from the memory cache, so a list scrolled back up shows images it already loaded at once. Images from the disk cache fade in.
223
+ - `'all'`: images from the memory or disk cache: images that download fade in, and local files and bundled images usually only the first time they load (the disk cache usually keeps them too).
224
+ - `'none'`: every image fades in.
225
+
226
+ Downloads, local files (`file://`, `content://`) and bundled images (`require()`) fade in. In lists that reuse views (e.g. FlashList), set `recyclingKey`, so a reused view starts empty and its image fades in, instead of showing the previous item's image until it loads.
227
+
228
+ ```jsx
229
+ <FastImage source={{ uri }} transition />
230
+ <FastImage source={{ uri }} transition={500} />
231
+ <FastImage source={{ uri }} transition={{ betweenImages: true, skipOnCacheHit: 'none' }} />
232
+ ```
233
+
234
+ ---
235
+
215
236
  ### `downsample?: boolean`
216
237
 
217
238
  iOS only. Decodes a large image at about the size it's shown at, instead of at full size, so it takes much less memory.
@@ -465,6 +486,16 @@ On Android, if your app has its own `AppGlideModule` (see [Are you using Glide a
465
486
 
466
487
  Images with `cache: 'web'` are kept in their own HTTP cache instead, up to 50 MB on each platform, which these limits don't change.
467
488
 
489
+ ## Web
490
+
491
+ With [react-native-web](https://necolas.github.io/react-native-web/), FastImage shows images with the web's `Image`. Bundlers pick the web version through the package's `browser` field, or the `.web.js` files next to the native ones.
492
+
493
+ Support is minimal:
494
+
495
+ - Props that work: `source` (a `uri`, or a `require()`d image), `defaultSource`, `resizeMode`, `tintColor`, `blurRadius`, `style`, children, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
496
+ - Ignored: `source.headers` (a browser can't send them for an image), `source.priority`, `source.cache`, `source.cacheKey`, `source.memoryCache`, `recyclingKey`, `loop`, `imageRendering`, `paused`, `transition`, `downsample`, `fallback` and `onProgress`.
497
+ - `FastImage.preload` loads the images into the browser's cache and resolves with a result per source. `clearMemoryCache`, `clearDiskCache` and `configureCache` resolve without doing anything (the browser manages its cache), and `getCachePath` and `writeToCache` resolve with `{ ok: false, error: 'Not supported on the web' }`.
498
+
468
499
  ## Troubleshooting
469
500
 
470
501
  If you have any problems using this library try the steps in [troubleshooting](docs/troubleshooting.md) and see if they fix it.
@@ -105,6 +105,21 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
105
105
  view.setBlurRadius(PixelUtil.toPixelFromDIP(blurRadius));
106
106
  }
107
107
 
108
+ @ReactProp(name = "transitionDuration")
109
+ public void setTransitionDuration(FastImageViewWithUrl view, int transitionDuration) {
110
+ view.setTransitionDuration(transitionDuration);
111
+ }
112
+
113
+ @ReactProp(name = "transitionBetweenImages")
114
+ public void setTransitionBetweenImages(FastImageViewWithUrl view, boolean betweenImages) {
115
+ view.setTransitionBetweenImages(betweenImages);
116
+ }
117
+
118
+ @ReactProp(name = "transitionSkipOnCacheHit")
119
+ public void setTransitionSkipOnCacheHit(FastImageViewWithUrl view, @Nullable String skipOnCacheHit) {
120
+ view.setTransitionSkipOnCacheHit(skipOnCacheHit);
121
+ }
122
+
108
123
  @ReactProp(name = "paused")
109
124
  public void setPaused(FastImageViewWithUrl view, boolean paused) {
110
125
  view.setPaused(paused);
@@ -2,9 +2,16 @@ package com.dylanvann.fastimage;
2
2
 
3
3
  import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
4
4
 
5
+ import android.animation.Animator;
6
+ import android.animation.AnimatorListenerAdapter;
7
+ import android.animation.ValueAnimator;
5
8
  import android.annotation.SuppressLint;
6
9
  import android.content.Context;
7
10
  import android.graphics.Bitmap;
11
+ import android.graphics.Canvas;
12
+ import android.graphics.Paint;
13
+ import android.graphics.PorterDuff;
14
+ import android.graphics.PorterDuffXfermode;
8
15
  import android.graphics.drawable.BitmapDrawable;
9
16
  import android.graphics.drawable.Drawable;
10
17
 
@@ -13,15 +20,20 @@ import androidx.annotation.Nullable;
13
20
  import androidx.appcompat.widget.AppCompatImageView;
14
21
  import androidx.core.view.ViewCompat;
15
22
 
23
+ import com.bumptech.glide.GenericTransitionOptions;
16
24
  import com.bumptech.glide.RequestBuilder;
17
25
  import com.bumptech.glide.RequestManager;
26
+ import com.bumptech.glide.load.DataSource;
18
27
  import com.bumptech.glide.load.model.GlideUrl;
19
28
  import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
29
+ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
20
30
  import com.bumptech.glide.load.resource.gif.GifDrawable;
21
31
  import com.bumptech.glide.request.RequestOptions;
22
32
  import com.bumptech.glide.request.target.DrawableImageViewTarget;
23
33
  import com.bumptech.glide.request.target.SizeReadyCallback;
34
+ import com.bumptech.glide.request.transition.NoTransition;
24
35
  import com.bumptech.glide.request.transition.Transition;
36
+ import com.bumptech.glide.request.transition.TransitionFactory;
25
37
  import com.facebook.react.bridge.ReadableMap;
26
38
  import com.facebook.react.bridge.WritableMap;
27
39
  import com.facebook.react.bridge.WritableNativeMap;
@@ -88,6 +100,168 @@ class FastImageViewWithUrl extends AppCompatImageView {
88
100
  // Pauses GIFs on the frame they're showing (the view's own animation).
89
101
  private boolean mPaused = false;
90
102
 
103
+ // The `transition` prop (from the next load): how long a loaded image
104
+ // takes to fade in, in milliseconds (0 for no fade), whether it also fades
105
+ // over a loaded image (a new source), and which cache hits show at once
106
+ // (none, memory or all).
107
+ private int mTransitionDuration = 0;
108
+ private boolean mTransitionBetweenImages = false;
109
+ private String mTransitionSkipOnCacheHit = "memory";
110
+
111
+ public void setTransitionDuration(int transitionDuration) {
112
+ mTransitionDuration = Math.max(0, transitionDuration);
113
+ }
114
+
115
+ public void setTransitionBetweenImages(boolean betweenImages) {
116
+ mTransitionBetweenImages = betweenImages;
117
+ }
118
+
119
+ public void setTransitionSkipOnCacheHit(@Nullable String skipOnCacheHit) {
120
+ mTransitionSkipOnCacheHit = skipOnCacheHit == null ? "memory" : skipOnCacheHit;
121
+ }
122
+
123
+ // Fades a loaded image in, unless it's from a cache that skipOnCacheHit
124
+ // skips, as Glide's and Coil's cross-fades and Fresco decide: from the
125
+ // memory cache it shows at once (by default), and from the disk cache
126
+ // too with 'all'. Downloads, local files and bundled images fade.
127
+ private final class FadeFactory implements TransitionFactory<Drawable> {
128
+ private final int duration;
129
+ private final String skipOnCacheHit;
130
+
131
+ FadeFactory(int duration, String skipOnCacheHit) {
132
+ this.duration = duration;
133
+ this.skipOnCacheHit = skipOnCacheHit;
134
+ }
135
+
136
+ @Override
137
+ public Transition<Drawable> build(DataSource dataSource, boolean isFirstResource) {
138
+ if (skips(dataSource)) return NoTransition.get();
139
+ return new Transition<Drawable>() {
140
+ @Override
141
+ public boolean transition(Drawable current, ViewAdapter adapter) {
142
+ startFade(duration);
143
+ // The target shows the image as usual.
144
+ return false;
145
+ }
146
+ };
147
+ }
148
+
149
+ private boolean skips(DataSource dataSource) {
150
+ if (skipOnCacheHit.equals("none")) return false;
151
+ if (dataSource == DataSource.MEMORY_CACHE) return true;
152
+ // Glide also keeps a local file's or bundled image's decoded
153
+ // image in its disk cache (at the size it was decoded at, and not
154
+ // for GIFs), so with 'all' they usually only fade the first time.
155
+ boolean disk = dataSource == DataSource.DATA_DISK_CACHE || dataSource == DataSource.RESOURCE_DISK_CACHE;
156
+ return disk && skipOnCacheHit.equals("all");
157
+ }
158
+ }
159
+
160
+ // A fade in progress: what the view showed (null if nothing), drawn
161
+ // fading out while the view's image fades in, as a cross-dissolve on iOS.
162
+ // Drawn here rather than with Glide's DrawableCrossFadeTransition, whose
163
+ // TransitionDrawable stretches both images to one size and stays in the
164
+ // view after the fade, still drawing (and animating) the previous image.
165
+ @Nullable
166
+ private ValueAnimator mFade;
167
+ @Nullable
168
+ private Bitmap mFadeFrom;
169
+ private float mFadeProgress = 1f;
170
+ private final Paint mFadeFromPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
171
+ private final Paint mFadeInPaint = new Paint();
172
+
173
+ {
174
+ // The two add up to a cross-dissolve: from * (1 - t) + image * t.
175
+ mFadeInPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
176
+ }
177
+
178
+ private void startFade(int duration) {
179
+ int width = getWidth();
180
+ int height = getHeight();
181
+ if (width <= 0 || height <= 0) return;
182
+ // What the view shows now, including a fade in progress.
183
+ Bitmap from = null;
184
+ if (getDrawable() != null) {
185
+ try {
186
+ from = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
187
+ onDraw(new Canvas(from));
188
+ } catch (OutOfMemoryError | RuntimeException e) {
189
+ // Out of memory, or a hardware bitmap (Android 8+; an app's
190
+ // Glide module can enable them), which a software canvas
191
+ // can't draw: fade in over nothing instead.
192
+ if (from != null) from.recycle();
193
+ from = null;
194
+ }
195
+ }
196
+ endFade();
197
+ mFadeFrom = from;
198
+ mFadeProgress = 0f;
199
+ final ValueAnimator fade = ValueAnimator.ofFloat(0f, 1f);
200
+ fade.setDuration(duration);
201
+ fade.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
202
+ @Override
203
+ public void onAnimationUpdate(ValueAnimator animation) {
204
+ mFadeProgress = (float) animation.getAnimatedValue();
205
+ invalidate();
206
+ }
207
+ });
208
+ fade.addListener(new AnimatorListenerAdapter() {
209
+ @Override
210
+ public void onAnimationEnd(Animator animation) {
211
+ if (mFade == fade) endFade();
212
+ }
213
+ });
214
+ mFade = fade;
215
+ fade.start();
216
+ }
217
+
218
+ // Shows the image on its own (the fade ends, another starts, or the view
219
+ // is detached).
220
+ private void endFade() {
221
+ ValueAnimator fade = mFade;
222
+ mFade = null;
223
+ if (fade != null) fade.cancel();
224
+ if (mFadeFrom != null) {
225
+ mFadeFrom.recycle();
226
+ mFadeFrom = null;
227
+ }
228
+ mFadeProgress = 1f;
229
+ invalidate();
230
+ }
231
+
232
+ @SuppressWarnings("deprecation")
233
+ @Override
234
+ protected void onDraw(Canvas canvas) {
235
+ if (mFade == null) {
236
+ super.onDraw(canvas);
237
+ return;
238
+ }
239
+ int width = getWidth();
240
+ int height = getHeight();
241
+ int alpha = Math.round(255 * mFadeProgress);
242
+ if (mFadeFrom == null) {
243
+ // Fading in over nothing: one layer.
244
+ int count = canvas.saveLayerAlpha(0, 0, width, height, alpha, Canvas.ALL_SAVE_FLAG);
245
+ super.onDraw(canvas);
246
+ canvas.restoreToCount(count);
247
+ return;
248
+ }
249
+ // Canvas.ALL_SAVE_FLAG: the saveLayer without flags is API 21+.
250
+ int count = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG);
251
+ mFadeFromPaint.setAlpha(255 - alpha);
252
+ canvas.drawBitmap(mFadeFrom, 0, 0, mFadeFromPaint);
253
+ mFadeInPaint.setAlpha(alpha);
254
+ canvas.saveLayer(0, 0, width, height, mFadeInPaint, Canvas.ALL_SAVE_FLAG);
255
+ super.onDraw(canvas);
256
+ canvas.restoreToCount(count);
257
+ }
258
+
259
+ @Override
260
+ protected void onDetachedFromWindow() {
261
+ endFade();
262
+ super.onDetachedFromWindow();
263
+ }
264
+
91
265
  public void setPaused(boolean paused) {
92
266
  if (paused == mPaused) return;
93
267
  mPaused = paused;
@@ -277,9 +451,10 @@ class FastImageViewWithUrl extends AppCompatImageView {
277
451
  mRecyclingKey = recyclingKey;
278
452
  if (changed) {
279
453
  // No thumbnail of the current image, so Glide clears the view to
280
- // defaultSource (or nothing) while the next one loads. Reload even
281
- // if the source is the same.
454
+ // defaultSource (or nothing) while the next one loads, nor a fade
455
+ // from it. Reload even if the source is the same.
282
456
  mShownRequest = null;
457
+ endFade();
283
458
  mNeedsReload = true;
284
459
  }
285
460
  }
@@ -299,6 +474,13 @@ class FastImageViewWithUrl extends AppCompatImageView {
299
474
  @Override
300
475
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
301
476
  super.onSizeChanged(w, h, oldw, oldh);
477
+ // During a fade, what the view showed was drawn at the old size: the
478
+ // image fades in over nothing instead.
479
+ if (mFadeFrom != null && (w != mFadeFrom.getWidth() || h != mFadeFrom.getHeight())) {
480
+ mFadeFrom.recycle();
481
+ mFadeFrom = null;
482
+ invalidate();
483
+ }
302
484
  reloadIfResized();
303
485
  }
304
486
 
@@ -395,7 +577,9 @@ class FastImageViewWithUrl extends AppCompatImageView {
395
577
  // it was loaded at: the key it's in Glide's memory cache under.
396
578
  @SuppressLint("CheckResult")
397
579
  private RequestBuilder<Drawable> fromCache(RequestBuilder<Drawable> shown) {
398
- RequestBuilder<Drawable> request = shown.clone().onlyRetrieveFromCache(true);
580
+ // Never fades: it's the image the view showed.
581
+ RequestBuilder<Drawable> request = shown.clone().onlyRetrieveFromCache(true)
582
+ .transition(GenericTransitionOptions.<Drawable>withNoTransition());
399
583
  if (mShownWidth > 0 && mShownHeight > 0) request = request.override(mShownWidth, mShownHeight);
400
584
  return request;
401
585
  }
@@ -461,6 +645,8 @@ class FastImageViewWithUrl extends AppCompatImageView {
461
645
  boolean restarting = mRestarting;
462
646
  mRestarting = false;
463
647
  mLoadCount++;
648
+ // A fade in progress carries on, as on iOS: into the next image if it
649
+ // shows at once, and a next image that fades starts from it.
464
650
  RequestBuilder<Drawable> shownRequest = mShownRequest;
465
651
  mShownRequest = null;
466
652
  mLoadingRequest = null;
@@ -560,6 +746,16 @@ class FastImageViewWithUrl extends AppCompatImageView {
560
746
 
561
747
  if (model == null) meanwhile = null;
562
748
  boolean thumbnail = shownRequest != null && model != null && meanwhile == null;
749
+ // After the clone: loading the image that's showing again (at a
750
+ // new size, or with another blur) doesn't fade it in again. Over a
751
+ // loaded image (a new source) only with betweenImages: otherwise it
752
+ // replaces it at once, as Glide, Coil and Fresco fade an image in
753
+ // from nothing and never between images.
754
+ boolean replacing = shownRequest != null && model != null;
755
+ if (mTransitionDuration > 0 && (!replacing || mTransitionBetweenImages)) {
756
+ builder = builder.transition(DrawableTransitionOptions.with(
757
+ new FadeFactory(mTransitionDuration, mTransitionSkipOnCacheHit)));
758
+ }
563
759
  if (thumbnail) {
564
760
  builder = builder.thumbnail(fromCache(shownRequest));
565
761
  }
@@ -0,0 +1,16 @@
1
+ export declare const resizeMode: {
2
+ readonly contain: 'contain';
3
+ readonly cover: 'cover';
4
+ readonly stretch: 'stretch';
5
+ readonly center: 'center';
6
+ };
7
+ export declare const priority: {
8
+ readonly low: 'low';
9
+ readonly normal: 'normal';
10
+ readonly high: 'high';
11
+ };
12
+ export declare const cacheControl: {
13
+ readonly immutable: 'immutable';
14
+ readonly web: 'web';
15
+ readonly cacheOnly: 'cacheOnly';
16
+ };
package/dist/index.cjs.js CHANGED
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  let react = require("react");
28
28
  react = __toESM(react);
29
29
  let react_native = require("react-native");
30
- //#region src/index.tsx
30
+ //#region src/constants.ts
31
31
  const resizeMode = {
32
32
  contain: "contain",
33
33
  cover: "cover",
@@ -44,6 +44,8 @@ const cacheControl = {
44
44
  web: "web",
45
45
  cacheOnly: "cacheOnly"
46
46
  };
47
+ //#endregion
48
+ //#region src/index.tsx
47
49
  const resolveDefaultSource = (defaultSource) => {
48
50
  if (!defaultSource) return null;
49
51
  if (react_native.Platform.OS === "android") {
@@ -91,11 +93,24 @@ function withProgress(onProgress) {
91
93
  onProgress(event);
92
94
  });
93
95
  }
96
+ const DEFAULT_FADE_MS = react_native.Platform.OS === "ios" ? 250 : 300;
97
+ function transitionProps(transition) {
98
+ var _transition$duration;
99
+ const { duration, betweenImages, skipOnCacheHit } = typeof transition === "number" ? { duration: transition } : typeof transition === "object" && transition ? {
100
+ ...transition,
101
+ duration: (_transition$duration = transition.duration) !== null && _transition$duration !== void 0 ? _transition$duration : DEFAULT_FADE_MS
102
+ } : { duration: transition ? DEFAULT_FADE_MS : 0 };
103
+ return {
104
+ transitionDuration: typeof duration === "number" && duration > 0 ? duration : 0,
105
+ transitionBetweenImages: !!betweenImages,
106
+ transitionSkipOnCacheHit: skipOnCacheHit || "memory"
107
+ };
108
+ }
94
109
  function withoutCache(source) {
95
110
  const { cache: _cache, ...rest } = source || {};
96
111
  return rest;
97
112
  }
98
- function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
113
+ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, transition, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
99
114
  const { onClick, ...props } = viewProps;
100
115
  const fallbackResult = (0, react.useRef)(void 0);
101
116
  const wrapperProps = {
@@ -158,6 +173,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
158
173
  ...imageProps,
159
174
  tintColor: resolvedTintColor,
160
175
  loopCount: loopCount(loop),
176
+ ...transitionProps(transition),
161
177
  style: react_native.StyleSheet.absoluteFill,
162
178
  source: resolvedSource,
163
179
  defaultSource: resolvedDefaultSource,
@@ -72,6 +72,12 @@ export type CachePathResult =
72
72
  | {| ok: true, path: string |}
73
73
  | {| ok: false, error: string |}
74
74
 
75
+ export type Transition = $ReadOnly<{|
76
+ duration?: ?number,
77
+ betweenImages?: ?boolean,
78
+ skipOnCacheHit?: ?('none' | 'memory' | 'all'),
79
+ |}>
80
+
75
81
  export type PreloadSuccess = {|
76
82
  uri: string,
77
83
  ok: true,
@@ -117,6 +123,7 @@ export type FastImageProps = $ReadOnly<{|
117
123
  loop?: ?(boolean | number),
118
124
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
119
125
  paused?: ?boolean,
126
+ transition?: ?(boolean | number | Transition),
120
127
  downsample?: ?boolean,
121
128
  blurRadius?: ?number,
122
129
  testID?: ?string,
@@ -0,0 +1,193 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ //#region \0rolldown/runtime.js
6
+ var __create = Object.create;
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __getProtoOf = Object.getPrototypeOf;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
14
+ key = keys[i];
15
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+ //#endregion
27
+ let react = require("react");
28
+ react = __toESM(react);
29
+ let react_native = require("react-native");
30
+ //#region src/constants.ts
31
+ const resizeMode = {
32
+ contain: "contain",
33
+ cover: "cover",
34
+ stretch: "stretch",
35
+ center: "center"
36
+ };
37
+ const priority = {
38
+ low: "low",
39
+ normal: "normal",
40
+ high: "high"
41
+ };
42
+ const cacheControl = {
43
+ immutable: "immutable",
44
+ web: "web",
45
+ cacheOnly: "cacheOnly"
46
+ };
47
+ //#endregion
48
+ //#region src/index.web.tsx
49
+ const notSupported = {
50
+ ok: false,
51
+ error: "Not supported on the web"
52
+ };
53
+ function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, resizeMode: mode = "cover", forwardedRef, onLayout, pointerEvents, fallback: _fallback, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
54
+ const result = (0, react.useRef)(void 0);
55
+ const sent = (0, react.useRef)(Promise.resolve());
56
+ const image = (0, react.useRef)(null);
57
+ const uri = typeof source === "object" && source ? source.uri : void 0;
58
+ const webSource = typeof source === "number" ? source : uri ? { uri } : void 0;
59
+ return /* @__PURE__ */ react.default.createElement(react_native.View, {
60
+ style: [styles.container, style],
61
+ onLayout,
62
+ pointerEvents,
63
+ ref: forwardedRef
64
+ }, /* @__PURE__ */ react.default.createElement(react_native.Image, {
65
+ ...props,
66
+ style: styles.image,
67
+ source: webSource,
68
+ defaultSource,
69
+ resizeMode: mode,
70
+ tintColor,
71
+ blurRadius,
72
+ ref: image,
73
+ onLoadStart,
74
+ onLoad: (event) => {
75
+ var _event$nativeEvent;
76
+ sent.current = loadedSize(event === null || event === void 0 || (_event$nativeEvent = event.nativeEvent) === null || _event$nativeEvent === void 0 ? void 0 : _event$nativeEvent.target, image.current, uri).then(({ width, height }) => {
77
+ result.current = {
78
+ ok: true,
79
+ width,
80
+ height
81
+ };
82
+ onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
83
+ width,
84
+ height
85
+ } });
86
+ });
87
+ },
88
+ onError: (event) => {
89
+ var _event$nativeEvent$er, _event$nativeEvent2;
90
+ const error = String((_event$nativeEvent$er = event === null || event === void 0 || (_event$nativeEvent2 = event.nativeEvent) === null || _event$nativeEvent2 === void 0 ? void 0 : _event$nativeEvent2.error) !== null && _event$nativeEvent$er !== void 0 ? _event$nativeEvent$er : "Failed to load the image");
91
+ result.current = {
92
+ ok: false,
93
+ error
94
+ };
95
+ sent.current = Promise.resolve();
96
+ onError === null || onError === void 0 || onError({ nativeEvent: { error } });
97
+ },
98
+ onLoadEnd: () => {
99
+ sent.current.then(() => {
100
+ var _result$current;
101
+ onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd((_result$current = result.current) !== null && _result$current !== void 0 ? _result$current : {
102
+ ok: false,
103
+ error: "Failed to load the image"
104
+ });
105
+ result.current = void 0;
106
+ });
107
+ }
108
+ }), children);
109
+ }
110
+ const FastImageMemo = (0, react.memo)(FastImageBase);
111
+ const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
112
+ forwardedRef: ref,
113
+ ...props
114
+ }));
115
+ FastImageComponent.displayName = "FastImage";
116
+ async function loadedSize(loaded, view, uri) {
117
+ var _view$querySelector;
118
+ if (loaded === null || loaded === void 0 ? void 0 : loaded.naturalWidth) return {
119
+ width: loaded.naturalWidth,
120
+ height: loaded.naturalHeight
121
+ };
122
+ await new Promise((resolve) => setTimeout(resolve, 0));
123
+ const shown = view === null || view === void 0 || (_view$querySelector = view.querySelector) === null || _view$querySelector === void 0 ? void 0 : _view$querySelector.call(view, "img");
124
+ if (shown === null || shown === void 0 ? void 0 : shown.naturalWidth) return {
125
+ width: shown.naturalWidth,
126
+ height: shown.naturalHeight
127
+ };
128
+ const src = uri !== null && uri !== void 0 ? uri : shown === null || shown === void 0 ? void 0 : shown.src;
129
+ return src ? getSize(src) : {
130
+ width: 0,
131
+ height: 0
132
+ };
133
+ }
134
+ function getSize(uri) {
135
+ return new Promise((resolve) => {
136
+ react_native.Image.getSize(uri, (width, height) => resolve({
137
+ width,
138
+ height
139
+ }), () => resolve({
140
+ width: 0,
141
+ height: 0
142
+ }));
143
+ });
144
+ }
145
+ const FastImage = FastImageComponent;
146
+ FastImage.resizeMode = resizeMode;
147
+ FastImage.cacheControl = cacheControl;
148
+ FastImage.priority = priority;
149
+ FastImage.preload = (sources) => Promise.all(sources.map((source) => {
150
+ const uri = source ? source.uri : void 0;
151
+ if (typeof uri !== "string" || !uri) return Promise.resolve({
152
+ ok: false,
153
+ error: "Invalid source: no uri",
154
+ uri
155
+ });
156
+ return react_native.Image.prefetch(uri).then(() => getSize(uri).then((size) => ({
157
+ ok: true,
158
+ uri,
159
+ ...size
160
+ })), () => ({
161
+ ok: false,
162
+ error: "Failed to load the image",
163
+ uri
164
+ }));
165
+ }));
166
+ FastImage.clearMemoryCache = () => Promise.resolve();
167
+ FastImage.clearDiskCache = () => Promise.resolve();
168
+ FastImage.configureCache = () => Promise.resolve({});
169
+ FastImage.writeToCache = () => Promise.resolve(notSupported);
170
+ FastImage.getCachePath = () => Promise.resolve(notSupported);
171
+ const styles = react_native.StyleSheet.create({
172
+ container: { overflow: "hidden" },
173
+ image: {
174
+ position: "absolute",
175
+ top: 0,
176
+ left: 0,
177
+ width: "100%",
178
+ height: "100%"
179
+ }
180
+ });
181
+ const FastImageWithRef = FastImageComponent;
182
+ const FastImageBackground = (0, react.forwardRef)(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ react.default.createElement(react_native.View, {
183
+ style,
184
+ ref
185
+ }, /* @__PURE__ */ react.default.createElement(FastImageWithRef, {
186
+ ...props,
187
+ style: [react_native.StyleSheet.absoluteFill, imageStyle],
188
+ ref: imageRef
189
+ }), children));
190
+ FastImageBackground.displayName = "FastImageBackground";
191
+ //#endregion
192
+ exports.FastImageBackground = FastImageBackground;
193
+ exports.default = FastImage;