react-native-fast-image 10.5.0 → 10.7.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 +26 -4
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +19 -14
- package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +97 -29
- package/dist/composeRefs.d.ts +2 -0
- package/dist/index.cjs.js +33 -25
- package/dist/index.cjs.js.flow +3 -1
- package/dist/index.cjs.web.js +52 -13
- package/dist/index.cjs.web.js.flow +3 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.js +34 -26
- package/dist/index.js.flow +3 -1
- package/dist/index.web.d.ts +5 -3
- package/dist/index.web.js +53 -14
- package/dist/index.web.js.flow +3 -1
- package/dist/specs/FastImageViewNativeComponent.d.ts +5 -2
- package/ios/FastImage/FFFThumbHash.h +3 -0
- package/ios/FastImage/FFFThumbHash.m +5 -1
- package/ios/FastImage/FFFastImageView.h +13 -7
- package/ios/FastImage/FFFastImageView.m +31 -19
- package/ios/FastImage/FFFastImageViewComponentView.mm +72 -14
- package/package.json +1 -1
- package/src/composeRefs.ts +29 -0
- package/src/index.js.flow +3 -1
- package/src/index.tsx +64 -81
- package/src/index.web.tsx +99 -53
- package/src/specs/FastImageViewNativeComponent.ts +25 -16
package/README.md
CHANGED
|
@@ -101,13 +101,13 @@ const YourImage = () => (
|
|
|
101
101
|
|
|
102
102
|
### `FastImage`
|
|
103
103
|
|
|
104
|
-
It's a single native view, which also takes React Native's View props, such as accessibility props, `testID`, `nativeID`, `onLayout`, `pointerEvents` and `hitSlop`. A ref to it is that view (e.g. for `measure`). It doesn't take children: use [`FastImageBackground`](#fastimagebackground) for content over an image.
|
|
104
|
+
It's a single native view, which also takes React Native's View props, such as accessibility props, `testID`, `nativeID`, `onLayout`, `pointerEvents` and `hitSlop`. A ref to it is that view (e.g. for `measure`), with [`reload()`](#refreload). It doesn't take children: use [`FastImageBackground`](#fastimagebackground) for content over an image.
|
|
105
105
|
|
|
106
106
|
<!-- api:props start (generated from src/ by website/scripts/generate.mts) -->
|
|
107
107
|
|
|
108
108
|
#### `source`
|
|
109
109
|
|
|
110
|
-
**Type:** `number | Source | Source[]`
|
|
110
|
+
**Type:** `number | Source | (number | Source)[]`
|
|
111
111
|
|
|
112
112
|
Source for the remote image to load: a `Source`, a `require()`d image, or an array of the same image at different sizes.
|
|
113
113
|
|
|
@@ -129,6 +129,7 @@ When `source` changes, the image that's showing stays until the new one has load
|
|
|
129
129
|
- When the view's size changes so that another size fits better, it loads that one (with its load events), and keeps showing the current image until then, without a `transition`.
|
|
130
130
|
- Each size is cached separately. A `cacheKey` applies to its own entry: give each size its own key (or none), or the sizes would replace each other in the cache.
|
|
131
131
|
- `FastImage.preload`, `getCachePath` and `writeToCache` take one size: pass the one a view will show. An array fails with `{ ok: false, error }`.
|
|
132
|
+
- A `require()`d image can be one of the sizes: its size is the file's.
|
|
132
133
|
- An array of one is the same as that source.
|
|
133
134
|
|
|
134
135
|
---
|
|
@@ -291,7 +292,7 @@ Notes:
|
|
|
291
292
|
|
|
292
293
|
**Type:** `number`
|
|
293
294
|
|
|
294
|
-
**Deprecated:** Use `placeholder`, which does the same.
|
|
295
|
+
**Deprecated:** Use `placeholder`, which does the same. With both, `placeholder` shows.
|
|
295
296
|
|
|
296
297
|
---
|
|
297
298
|
|
|
@@ -536,6 +537,27 @@ If supplied, changes the color of all the non-transparent pixels to the given co
|
|
|
536
537
|
|
|
537
538
|
<!-- api:props end -->
|
|
538
539
|
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
#### `ref.reload()`
|
|
543
|
+
|
|
544
|
+
Loads the image again, e.g. to try again after `onError`. The image showing stays until the new one has loaded (or the placeholder, if it failed), and the events are sent as for a new source. An image in the cache loads from it. In TypeScript, the ref's type is `FastImageRef`.
|
|
545
|
+
|
|
546
|
+
```tsx
|
|
547
|
+
const image = useRef<FastImageRef>(null)
|
|
548
|
+
|
|
549
|
+
<FastImage ref={image} source={{ uri }} onError={() => setFailed(true)} />
|
|
550
|
+
{failed && (
|
|
551
|
+
<Button
|
|
552
|
+
title="Try again"
|
|
553
|
+
onPress={() => {
|
|
554
|
+
setFailed(false)
|
|
555
|
+
image.current?.reload()
|
|
556
|
+
}}
|
|
557
|
+
/>
|
|
558
|
+
)}
|
|
559
|
+
```
|
|
560
|
+
|
|
539
561
|
### `FastImageBackground`
|
|
540
562
|
|
|
541
563
|
An image with content on top of it: a view that the image fills, with the children on top.
|
|
@@ -576,7 +598,7 @@ The image's style.
|
|
|
576
598
|
|
|
577
599
|
#### `imageRef`
|
|
578
600
|
|
|
579
|
-
**Type:** `Ref<
|
|
601
|
+
**Type:** `Ref<FastImageRef>`
|
|
580
602
|
|
|
581
603
|
A ref to the image (the FastImage inside).
|
|
582
604
|
|
|
@@ -21,6 +21,7 @@ import com.bumptech.glide.RequestManager;
|
|
|
21
21
|
import com.facebook.react.bridge.ColorPropConverter;
|
|
22
22
|
import com.facebook.react.bridge.Dynamic;
|
|
23
23
|
import com.facebook.react.bridge.DynamicFromObject;
|
|
24
|
+
import com.facebook.react.bridge.JavaOnlyArray;
|
|
24
25
|
import com.facebook.react.bridge.ReadableArray;
|
|
25
26
|
import com.facebook.react.bridge.ReadableMap;
|
|
26
27
|
import com.facebook.react.bridge.ReadableType;
|
|
@@ -307,23 +308,26 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
|
|
|
307
308
|
|
|
308
309
|
@Override
|
|
309
310
|
public void setSource(FastImageViewWithUrl view, Dynamic source) {
|
|
310
|
-
|
|
311
|
+
// One source, or several (an array).
|
|
312
|
+
view.setSource(source.isNull() ? null
|
|
313
|
+
: source.getType() == ReadableType.Array ? source.asArray()
|
|
314
|
+
: JavaOnlyArray.of(source.asMap()));
|
|
311
315
|
}
|
|
312
316
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// The `placeholder` prop: a url to load apart from the image, as a source
|
|
319
|
-
// (a map), or the resolved asset's uri or a data uri (a string).
|
|
317
|
+
// The `placeholder` prop: a thumbhash or blurDataUri, or a url to load
|
|
318
|
+
// apart from the image, as a source (a map), or the resolved asset's uri
|
|
319
|
+
// or a data uri (a string).
|
|
320
320
|
@Override
|
|
321
321
|
public void setPlaceholder(FastImageViewWithUrl view, Dynamic source) {
|
|
322
322
|
if (source.getType() == ReadableType.Map) {
|
|
323
|
+
ReadableMap map = source.asMap();
|
|
324
|
+
boolean hash = map.hasKey("thumbhash") || map.hasKey("blurDataUri");
|
|
323
325
|
view.setPlaceholder(null);
|
|
324
|
-
view.
|
|
326
|
+
view.setPlaceholderHash(hash ? FastImageViewWithUrl.sourcePlaceholder(map) : null);
|
|
327
|
+
view.setPlaceholderSource(hash ? null : map);
|
|
325
328
|
return;
|
|
326
329
|
}
|
|
330
|
+
view.setPlaceholderHash(null);
|
|
327
331
|
view.setPlaceholderSource(null);
|
|
328
332
|
String uri = source.isNull() ? null : source.asString();
|
|
329
333
|
if (uri != null && FastImageThumbHash.isDataUri(uri)) {
|
|
@@ -351,11 +355,6 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
|
|
|
351
355
|
view.setRecyclingKey(recyclingKey);
|
|
352
356
|
}
|
|
353
357
|
|
|
354
|
-
@Override
|
|
355
|
-
public void setSourcePlaceholders(FastImageViewWithUrl view, @Nullable ReadableArray placeholders) {
|
|
356
|
-
view.setSourcePlaceholders(placeholders);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
358
|
@Override
|
|
360
359
|
public void setLoopCount(FastImageViewWithUrl view, int loopCount) {
|
|
361
360
|
view.setLoopCount(loopCount);
|
|
@@ -436,6 +435,12 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
|
|
|
436
435
|
view.setObjectFit(FastImageViewConverter.getScaleType(objectFit));
|
|
437
436
|
}
|
|
438
437
|
|
|
438
|
+
// The ref's reload() (a command).
|
|
439
|
+
@Override
|
|
440
|
+
public void reload(FastImageViewWithUrl view) {
|
|
441
|
+
view.reload();
|
|
442
|
+
}
|
|
443
|
+
|
|
439
444
|
@Override
|
|
440
445
|
public void onDropViewInstance(@NonNull FastImageViewWithUrl view) {
|
|
441
446
|
// Cancels its request, and nothing loads or tracks progress for it
|
|
@@ -48,6 +48,7 @@ import com.bumptech.glide.request.transition.Transition;
|
|
|
48
48
|
import com.bumptech.glide.request.transition.TransitionFactory;
|
|
49
49
|
import com.facebook.react.bridge.ReadableArray;
|
|
50
50
|
import com.facebook.react.bridge.ReadableMap;
|
|
51
|
+
import com.facebook.react.bridge.ReadableType;
|
|
51
52
|
import com.facebook.react.bridge.WritableMap;
|
|
52
53
|
import com.facebook.react.bridge.WritableNativeMap;
|
|
53
54
|
import com.facebook.react.modules.i18nmanager.I18nUtil;
|
|
@@ -58,6 +59,10 @@ import com.facebook.react.uimanager.PointerEvents;
|
|
|
58
59
|
import com.facebook.react.uimanager.ReactPointerEventsView;
|
|
59
60
|
import com.facebook.react.uimanager.style.LogicalEdge;
|
|
60
61
|
|
|
62
|
+
import java.util.ArrayList;
|
|
63
|
+
import java.util.HashMap;
|
|
64
|
+
import java.util.List;
|
|
65
|
+
import java.util.Objects;
|
|
61
66
|
import javax.annotation.Nonnull;
|
|
62
67
|
|
|
63
68
|
class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEventsView, ReactHitSlopView {
|
|
@@ -213,16 +218,72 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
213
218
|
return false;
|
|
214
219
|
}
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
// The `source` prop: one source, or several (sizes of one image), each
|
|
222
|
+
// with its thumbhash or blurDataUri if it has one. Only a change besides
|
|
223
|
+
// those loads again: a new thumbhash for the same image (e.g. one that
|
|
224
|
+
// arrives later) shows at once (see updatePlaceholder).
|
|
225
|
+
public void setSource(@Nullable ReadableArray sources) {
|
|
226
|
+
List<Object> key = sourceKey(sources);
|
|
227
|
+
if (!mHasSource || !Objects.equals(key, mSourceKey)) {
|
|
228
|
+
mHasSource = true;
|
|
229
|
+
mSourceKey = key;
|
|
230
|
+
mNeedsReload = true;
|
|
231
|
+
mSources = sources != null && sources.size() > 1 ? sources : null;
|
|
232
|
+
mSourceIndex = -1;
|
|
233
|
+
if (mSources == null) mSource = sources != null && sources.size() == 1 ? map(sources, 0) : null;
|
|
234
|
+
}
|
|
235
|
+
mSourcePlaceholders = new ArrayList<>();
|
|
236
|
+
for (int i = 0; sources != null && i < sources.size(); i++) {
|
|
237
|
+
mSourcePlaceholders.add(sourcePlaceholder(map(sources, i)));
|
|
238
|
+
}
|
|
239
|
+
updatePlaceholder();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The sources without their thumbhashes and blurDataUris, to compare.
|
|
243
|
+
private boolean mHasSource = false;
|
|
244
|
+
@Nullable
|
|
245
|
+
private List<Object> mSourceKey = null;
|
|
246
|
+
|
|
247
|
+
@Nullable
|
|
248
|
+
private static List<Object> sourceKey(@Nullable ReadableArray sources) {
|
|
249
|
+
if (sources == null) return null;
|
|
250
|
+
List<Object> key = new ArrayList<>();
|
|
251
|
+
for (int i = 0; i < sources.size(); i++) {
|
|
252
|
+
ReadableMap source = map(sources, i);
|
|
253
|
+
key.add(source == null ? null : withoutPlaceholders(source));
|
|
254
|
+
}
|
|
255
|
+
return key;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// A source without its thumbhash and blurDataUri.
|
|
259
|
+
private static HashMap<String, Object> withoutPlaceholders(ReadableMap source) {
|
|
260
|
+
HashMap<String, Object> map = source.toHashMap();
|
|
261
|
+
map.remove("thumbhash");
|
|
262
|
+
map.remove("blurDataUri");
|
|
263
|
+
return map;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@Nullable
|
|
267
|
+
private static ReadableMap map(ReadableArray array, int index) {
|
|
268
|
+
return array.getType(index) == ReadableType.Map ? array.getMap(index) : null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// A source's (or `placeholder`'s) ThumbHash, or its blurDataUri if it's a
|
|
272
|
+
// data uri (JS warns about another), or null.
|
|
273
|
+
@Nullable
|
|
274
|
+
static String sourcePlaceholder(@Nullable ReadableMap source) {
|
|
275
|
+
if (source == null) return null;
|
|
276
|
+
String thumbhash = string(source, "thumbhash");
|
|
277
|
+
if (thumbhash != null && !thumbhash.isEmpty()) return thumbhash;
|
|
278
|
+
String blurDataUri = string(source, "blurDataUri");
|
|
279
|
+
return blurDataUri != null && FastImageThumbHash.isDataUri(blurDataUri) ? blurDataUri : null;
|
|
220
280
|
}
|
|
221
281
|
|
|
222
|
-
// The `source` prop. With several `sources`, mSource is the one picked for
|
|
223
|
-
// the view's size instead.
|
|
224
282
|
@Nullable
|
|
225
|
-
private ReadableMap
|
|
283
|
+
private static String string(ReadableMap map, String key) {
|
|
284
|
+
return map.hasKey(key) && map.getType(key) == ReadableType.String ? map.getString(key) : null;
|
|
285
|
+
}
|
|
286
|
+
|
|
226
287
|
// Several sources (2 or more) of the same image at different sizes, or
|
|
227
288
|
// null: the view loads the one whose size is closest to its own.
|
|
228
289
|
@Nullable
|
|
@@ -240,13 +301,6 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
240
301
|
@Nullable
|
|
241
302
|
private FastImageViewManager mManager;
|
|
242
303
|
|
|
243
|
-
public void setSources(@Nullable ReadableArray sources) {
|
|
244
|
-
mNeedsReload = true;
|
|
245
|
-
mSources = sources != null && sources.size() > 1 ? sources : null;
|
|
246
|
-
mSourceIndex = -1;
|
|
247
|
-
if (mSources == null) mSource = mPropSource;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
304
|
// The box the image is drawn in: the view inside its padding, which is the
|
|
251
305
|
// border widths (see updateBorderPadding).
|
|
252
306
|
private int contentWidth() {
|
|
@@ -310,19 +364,29 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
310
364
|
return true;
|
|
311
365
|
}
|
|
312
366
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
367
|
+
// Loads the image again, as a new source would, keeping the image showing
|
|
368
|
+
// until then (the ref's reload()). Before the first props there's nothing
|
|
369
|
+
// to load yet.
|
|
370
|
+
void reload() {
|
|
371
|
+
if (mManager == null) return;
|
|
316
372
|
mNeedsReload = true;
|
|
373
|
+
onAfterUpdate(mManager, requestManager);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Shows at once, as a new source placeholder does (see updatePlaceholder),
|
|
377
|
+
// without loading the image again.
|
|
378
|
+
public void setPlaceholder(@Nullable Drawable placeholder) {
|
|
317
379
|
mPlaceholder = placeholder;
|
|
380
|
+
updatePlaceholder();
|
|
318
381
|
}
|
|
319
382
|
|
|
320
|
-
// The
|
|
321
|
-
// for
|
|
322
|
-
|
|
323
|
-
//
|
|
383
|
+
// The sources' ThumbHashes or blurDataUris, by the source's index (null
|
|
384
|
+
// for none): the picked source's shows instead of the placeholder prop.
|
|
385
|
+
private List<String> mSourcePlaceholders = new ArrayList<>();
|
|
386
|
+
// `placeholder` as a thumbhash or blurDataUri: shows for the sources
|
|
387
|
+
// without their own.
|
|
324
388
|
@Nullable
|
|
325
|
-
private
|
|
389
|
+
private String mPlaceholderHash = null;
|
|
326
390
|
// What shows while the image loads and if it fails: the picked source's
|
|
327
391
|
// ThumbHash or blurDataUri decoded (FastImageThumbHash), or the placeholder
|
|
328
392
|
// prop. FastImage's own drawable either way, which Glide doesn't recycle.
|
|
@@ -337,8 +401,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
337
401
|
@Nullable
|
|
338
402
|
private String mInvalidPlaceholder = null;
|
|
339
403
|
|
|
340
|
-
public void
|
|
341
|
-
|
|
404
|
+
public void setPlaceholderHash(@Nullable String hash) {
|
|
405
|
+
if (Objects.equals(hash, mPlaceholderHash)) return;
|
|
406
|
+
mPlaceholderHash = hash;
|
|
342
407
|
updatePlaceholder();
|
|
343
408
|
}
|
|
344
409
|
|
|
@@ -450,13 +515,15 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
450
515
|
}
|
|
451
516
|
}
|
|
452
517
|
|
|
453
|
-
// The picked source's ThumbHash or blurDataUri, or
|
|
518
|
+
// The picked source's ThumbHash or blurDataUri, or `placeholder`'s, or
|
|
519
|
+
// null.
|
|
454
520
|
@Nullable
|
|
455
521
|
private String pickedSourcePlaceholder() {
|
|
456
522
|
int index = mSources == null ? 0 : mSourceIndex;
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
523
|
+
// None until one is picked.
|
|
524
|
+
if (index < 0) return null;
|
|
525
|
+
String key = index < mSourcePlaceholders.size() ? mSourcePlaceholders.get(index) : null;
|
|
526
|
+
return key != null ? key : mPlaceholderHash;
|
|
460
527
|
}
|
|
461
528
|
|
|
462
529
|
// Sets mShownPlaceholder for the picked source. A view showing the previous
|
|
@@ -1759,7 +1826,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1759
1826
|
// resolved: fire onError and onLoadEnd and show the placeholder prop,
|
|
1760
1827
|
// as on iOS (#1028, #945).
|
|
1761
1828
|
if (mSource != null && (imageSource == null || imageSource.getUri().toString().length() == 0)) {
|
|
1762
|
-
|
|
1829
|
+
// Without its thumbhash or blurDataUri (a data uri can be long).
|
|
1830
|
+
String error = "Invalid source: " + withoutPlaceholders(mSource);
|
|
1763
1831
|
WritableMap event = new WritableNativeMap();
|
|
1764
1832
|
event.putString("error", error);
|
|
1765
1833
|
FastImageEvents.send(this, REACT_ON_ERROR_EVENT, event);
|
package/dist/index.cjs.js
CHANGED
|
@@ -255,6 +255,7 @@ function objectPositionOrCentered(position) {
|
|
|
255
255
|
}
|
|
256
256
|
//#endregion
|
|
257
257
|
//#region src/specs/FastImageViewNativeComponent.ts
|
|
258
|
+
const Commands = (0, react_native.codegenNativeCommands)({ supportedCommands: ["reload"] });
|
|
258
259
|
var FastImageViewNativeComponent_default = (0, react_native.codegenNativeComponent)("FastImageView");
|
|
259
260
|
//#endregion
|
|
260
261
|
//#region src/specs/NativeFastImageModule.ts
|
|
@@ -279,17 +280,28 @@ function dataUri(value) {
|
|
|
279
280
|
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");
|
|
280
281
|
}
|
|
281
282
|
//#endregion
|
|
282
|
-
//#region src/
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
283
|
+
//#region src/composeRefs.ts
|
|
284
|
+
function composeRefs(...refs) {
|
|
285
|
+
return (value) => {
|
|
286
|
+
const cleanups = refs.map((ref) => {
|
|
287
|
+
if (typeof ref === "function") {
|
|
288
|
+
const cleanup = ref(value);
|
|
289
|
+
return typeof cleanup === "function" ? cleanup : () => {
|
|
290
|
+
ref(null);
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (ref) {
|
|
294
|
+
ref.current = value;
|
|
295
|
+
return () => {
|
|
296
|
+
ref.current = null;
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
return () => cleanups.forEach((cleanup) => cleanup === null || cleanup === void 0 ? void 0 : cleanup());
|
|
301
|
+
};
|
|
292
302
|
}
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region src/index.tsx
|
|
293
305
|
const resolvePlaceholder = (placeholder) => {
|
|
294
306
|
if (typeof placeholder === "string") {
|
|
295
307
|
if (isImageName(placeholder)) return react_native.Platform.OS === "android" ? placeholder : { uri: placeholder };
|
|
@@ -361,28 +373,21 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
361
373
|
const resolvedTintColor = tintColor != null ? tintColor : fromStyle(style, "tintColor");
|
|
362
374
|
const fit = resolveObjectFit(objectFit, style);
|
|
363
375
|
const position = objectPositionOrCentered(objectPosition);
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
sources: (sources === null || sources === void 0 ? void 0 : sources.some(hasSourcePlaceholder)) ? sources.map(withoutPlaceholders) : sources,
|
|
372
|
-
sourcePlaceholders: placeholders.some(Boolean) ? placeholders : void 0
|
|
373
|
-
};
|
|
374
|
-
}, [source, shared]);
|
|
375
|
-
const resolvedPlaceholder = (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(hasSourcePlaceholder(placeholder) ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
376
|
+
const resolvedSource = (0, react.useMemo)(() => {
|
|
377
|
+
for (const entry of [source].flat()) dataUri(entry === null || entry === void 0 ? void 0 : entry.blurDataUri);
|
|
378
|
+
const single = Array.isArray(source) && source.length === 1 ? source[0] : source;
|
|
379
|
+
return Array.isArray(single) ? single.map((entry) => react_native.Image.resolveAssetSource(entry)) : react_native.Image.resolveAssetSource(single);
|
|
380
|
+
}, [source]);
|
|
381
|
+
const resolvedPlaceholder = hasSourcePlaceholder(placeholder) ? placeholder.thumbhash || dataUri(placeholder.blurDataUri) ? placeholder : resolvePlaceholder(defaultSource) : (_placeholderSource = placeholderSource(placeholder)) !== null && _placeholderSource !== void 0 ? _placeholderSource : resolvePlaceholder(placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
382
|
+
const ref = (0, react.useMemo)(() => composeRefs(addReload, forwardedRef), [forwardedRef]);
|
|
376
383
|
return /* @__PURE__ */ react.default.createElement(FastImageViewNativeComponent_default, {
|
|
377
384
|
...props,
|
|
378
|
-
ref
|
|
385
|
+
ref,
|
|
379
386
|
style: [styles.image, style],
|
|
380
387
|
recyclingKey: (_props$recyclingKey = props.recyclingKey) !== null && _props$recyclingKey !== void 0 ? _props$recyclingKey : void 0,
|
|
381
388
|
loopCount: loopCount(loop),
|
|
382
389
|
...resolveTransition(transition, (_defaultTransition = defaultTransition) !== null && _defaultTransition !== void 0 ? _defaultTransition : builtInTransition()),
|
|
383
390
|
source: resolvedSource,
|
|
384
|
-
sources,
|
|
385
|
-
sourcePlaceholders,
|
|
386
391
|
placeholder: resolvedPlaceholder,
|
|
387
392
|
onFastImageLoadStart: onLoadStart,
|
|
388
393
|
onFastImageProgress: onProgress,
|
|
@@ -404,6 +409,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
404
409
|
objectPositionYOffset: position.y.offset
|
|
405
410
|
});
|
|
406
411
|
}
|
|
412
|
+
function addReload(view) {
|
|
413
|
+
if (view) view.reload = () => Commands.reload(view);
|
|
414
|
+
}
|
|
407
415
|
const FastImageMemo = (0, react.memo)(FastImageBase);
|
|
408
416
|
const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
|
|
409
417
|
forwardedRef: ref,
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -145,7 +145,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
145
|
onLoadStart?: ?() => void,
|
|
146
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
147
|
|
|
148
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
149
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
150
|
// Deprecated: use placeholder, which is the same.
|
|
151
151
|
defaultSource?: ?number,
|
|
@@ -196,4 +196,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
196
196
|
source: FastImageSource,
|
|
197
197
|
file: string,
|
|
198
198
|
) => Promise<CachePathResult>;
|
|
199
|
+
// On a ref: loads the image again, e.g. to try again after onError.
|
|
200
|
+
reload(): void;
|
|
199
201
|
}
|
package/dist/index.cjs.web.js
CHANGED
|
@@ -282,6 +282,27 @@ function dataUri(value) {
|
|
|
282
282
|
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");
|
|
283
283
|
}
|
|
284
284
|
//#endregion
|
|
285
|
+
//#region src/composeRefs.ts
|
|
286
|
+
function composeRefs(...refs) {
|
|
287
|
+
return (value) => {
|
|
288
|
+
const cleanups = refs.map((ref) => {
|
|
289
|
+
if (typeof ref === "function") {
|
|
290
|
+
const cleanup = ref(value);
|
|
291
|
+
return typeof cleanup === "function" ? cleanup : () => {
|
|
292
|
+
ref(null);
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (ref) {
|
|
296
|
+
ref.current = value;
|
|
297
|
+
return () => {
|
|
298
|
+
ref.current = null;
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
return () => cleanups.forEach((cleanup) => cleanup === null || cleanup === void 0 ? void 0 : cleanup());
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
285
306
|
//#region src/index.web.tsx
|
|
286
307
|
const notSupported = {
|
|
287
308
|
ok: false,
|
|
@@ -298,12 +319,17 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
298
319
|
const src = sized ? sized.src : resolveUri(source);
|
|
299
320
|
const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
|
|
300
321
|
const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
|
|
322
|
+
const [reloads, setReloads] = (0, react.useState)(0);
|
|
323
|
+
const ref = (0, react.useMemo)(() => composeRefs((view) => {
|
|
324
|
+
if (view) view.reload = () => setReloads((count) => count + 1);
|
|
325
|
+
}, forwardedRef), [forwardedRef]);
|
|
301
326
|
const priority = sourcePriority(source);
|
|
302
327
|
const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
|
|
303
328
|
const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
|
|
304
329
|
const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
|
|
330
|
+
const placeholderKey = `${urlPlaceholder} ${reloads}`;
|
|
305
331
|
const [failedPlaceholder, setFailedPlaceholder] = (0, react.useState)();
|
|
306
|
-
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview
|
|
332
|
+
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : urlPlaceholder ? void 0 : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
307
333
|
const [result, setResult] = (0, react.useState)();
|
|
308
334
|
const ok = result && result.key === key ? result.ok : void 0;
|
|
309
335
|
const svgId = (0, react.useId)().replace(/[^\w-]/g, "");
|
|
@@ -315,14 +341,14 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
315
341
|
const showDefault = ok !== true;
|
|
316
342
|
useClientLayoutEffect(() => {
|
|
317
343
|
if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
|
|
318
|
-
}, [key]);
|
|
344
|
+
}, [key, reloads]);
|
|
319
345
|
return /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
320
346
|
...viewProps,
|
|
321
347
|
"aria-label": label,
|
|
322
348
|
style: [styles.container, style],
|
|
323
349
|
onLayout,
|
|
324
350
|
pointerEvents,
|
|
325
|
-
ref
|
|
351
|
+
ref
|
|
326
352
|
}, defaultUri && showDefault ? /* @__PURE__ */ react.default.createElement(Picture, {
|
|
327
353
|
key: `default ${defaultUri}`,
|
|
328
354
|
src: defaultUri,
|
|
@@ -334,7 +360,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
334
360
|
filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
|
|
335
361
|
alt: key ? "" : label !== null && label !== void 0 ? label : ""
|
|
336
362
|
}) : null, urlPlaceholder && showDefault ? /* @__PURE__ */ react.default.createElement(Picture, {
|
|
337
|
-
key: `placeholder ${
|
|
363
|
+
key: `placeholder ${placeholderKey}`,
|
|
338
364
|
src: urlPlaceholder,
|
|
339
365
|
loading,
|
|
340
366
|
priority: "high",
|
|
@@ -342,11 +368,11 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
342
368
|
fit: fit === "scale-down" || fit === "none" ? "contain" : fit,
|
|
343
369
|
position,
|
|
344
370
|
filter: tintColor ? `url(#${tintId})` : void 0,
|
|
345
|
-
hidden: failedPlaceholder ===
|
|
346
|
-
onError: () => setFailedPlaceholder(
|
|
371
|
+
hidden: failedPlaceholder === placeholderKey,
|
|
372
|
+
onError: () => setFailedPlaceholder(placeholderKey),
|
|
347
373
|
alt: ""
|
|
348
374
|
}) : null, key && src ? /* @__PURE__ */ react.default.createElement(Picture, {
|
|
349
|
-
key: several ? "sizes" : key
|
|
375
|
+
key: `${several ? "sizes" : key} ${reloads}`,
|
|
350
376
|
src,
|
|
351
377
|
srcSet,
|
|
352
378
|
sizes,
|
|
@@ -416,10 +442,9 @@ function shared(source, key, preview) {
|
|
|
416
442
|
function resolveUri(source) {
|
|
417
443
|
let uri;
|
|
418
444
|
if (typeof source === "number") {
|
|
419
|
-
var _asset$scales$;
|
|
420
445
|
const asset = (0, react_native_web_dist_modules_AssetRegistry.getAssetByID)(source);
|
|
421
446
|
if (!asset) return void 0;
|
|
422
|
-
const scale =
|
|
447
|
+
const scale = assetScale(asset);
|
|
423
448
|
const suffix = scale !== 1 ? `@${scale}x` : "";
|
|
424
449
|
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`;
|
|
425
450
|
} else if (typeof source === "string") uri = source;
|
|
@@ -428,9 +453,24 @@ function resolveUri(source) {
|
|
|
428
453
|
const svg = uri === null || uri === void 0 ? void 0 : uri.match(/^(data:image\/svg\+xml;utf8,)(.*)/);
|
|
429
454
|
return svg ? svg[1] + encodeURIComponent(svg[2]) : uri || void 0;
|
|
430
455
|
}
|
|
431
|
-
function
|
|
456
|
+
function assetScale(asset) {
|
|
457
|
+
var _asset$scales$;
|
|
458
|
+
return asset.scales.reduce((best, scale) => Math.abs(scale - react_native.PixelRatio.get()) < Math.abs(best - react_native.PixelRatio.get()) ? scale : best, (_asset$scales$ = asset.scales[0]) !== null && _asset$scales$ !== void 0 ? _asset$scales$ : 1);
|
|
459
|
+
}
|
|
460
|
+
function sizedSource(source) {
|
|
461
|
+
if (typeof source !== "number") return source;
|
|
462
|
+
const asset = (0, react_native_web_dist_modules_AssetRegistry.getAssetByID)(source);
|
|
463
|
+
return asset ? {
|
|
464
|
+
uri: resolveUri(source),
|
|
465
|
+
width: asset.width,
|
|
466
|
+
height: asset.height,
|
|
467
|
+
scale: assetScale(asset)
|
|
468
|
+
} : void 0;
|
|
469
|
+
}
|
|
470
|
+
function sizedSources(entries) {
|
|
432
471
|
var _largest$uri, _sources$find;
|
|
433
|
-
const
|
|
472
|
+
const sources = entries.map(sizedSource);
|
|
473
|
+
const widths = sources.filter((source) => !!(source === null || source === void 0 ? void 0 : source.uri) && !!source.width).map((source) => {
|
|
434
474
|
var _source$width, _source$scale;
|
|
435
475
|
return {
|
|
436
476
|
uri: source.uri,
|
|
@@ -590,11 +630,10 @@ FastImage.setDefaults = () => {};
|
|
|
590
630
|
FastImage.writeToCache = () => Promise.resolve(notSupported);
|
|
591
631
|
FastImage.getCachePath = () => Promise.resolve(notSupported);
|
|
592
632
|
const styles = react_native.StyleSheet.create({ container: { overflow: "hidden" } });
|
|
593
|
-
const FastImageWithRef = FastImageComponent;
|
|
594
633
|
const FastImageBackground = (0, react.forwardRef)(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
595
634
|
style,
|
|
596
635
|
ref
|
|
597
|
-
}, /* @__PURE__ */ react.default.createElement(
|
|
636
|
+
}, /* @__PURE__ */ react.default.createElement(FastImageComponent, {
|
|
598
637
|
...props,
|
|
599
638
|
style: [react_native.StyleSheet.absoluteFill, imageStyle],
|
|
600
639
|
ref: imageRef
|
|
@@ -145,7 +145,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
145
|
onLoadStart?: ?() => void,
|
|
146
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
147
|
|
|
148
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
149
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
150
|
// Deprecated: use placeholder, which is the same.
|
|
151
151
|
defaultSource?: ?number,
|
|
@@ -196,4 +196,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
196
196
|
source: FastImageSource,
|
|
197
197
|
file: string,
|
|
198
198
|
) => Promise<CachePathResult>;
|
|
199
|
+
// On a ref: loads the image again, e.g. to try again after onError.
|
|
200
|
+
reload(): void;
|
|
199
201
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -331,9 +331,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
331
331
|
* - `FastImage.preload`, `getCachePath` and `writeToCache` take one size:
|
|
332
332
|
* pass the one a view will show. An array fails with
|
|
333
333
|
* `{ ok: false, error }`.
|
|
334
|
+
* - A `require()`d image can be one of the sizes: its size is the file's.
|
|
334
335
|
* - An array of one is the same as that source.
|
|
335
336
|
*/
|
|
336
|
-
source?: Source | Source
|
|
337
|
+
source?: Source | Array<Source | ImageRequireSource> | ImageRequireSource;
|
|
337
338
|
/**
|
|
338
339
|
* A placeholder shown while the image loads, and if it fails to load:
|
|
339
340
|
*
|
|
@@ -363,7 +364,8 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
363
364
|
*/
|
|
364
365
|
placeholder?: ImageRequireSource | string | SourcePlaceholder | PlaceholderSource;
|
|
365
366
|
/**
|
|
366
|
-
* @deprecated Use `placeholder`, which does the same.
|
|
367
|
+
* @deprecated Use `placeholder`, which does the same. With both,
|
|
368
|
+
* `placeholder` shows.
|
|
367
369
|
*/
|
|
368
370
|
defaultSource?: ImageRequireSource;
|
|
369
371
|
/**
|
|
@@ -662,6 +664,18 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
662
664
|
children?: "FastImage doesn't take children: use FastImageBackground for content over an image";
|
|
663
665
|
}
|
|
664
666
|
type ViewRef = React.ComponentRef<typeof View>;
|
|
667
|
+
/**
|
|
668
|
+
* What a ref to `FastImage` gets: its native view, with a View's methods
|
|
669
|
+
* (e.g. `measure`), and `reload()`. E.g. `useRef<FastImageRef>(null)`.
|
|
670
|
+
*/
|
|
671
|
+
export type FastImageRef = ViewRef & {
|
|
672
|
+
/**
|
|
673
|
+
* Loads the image again, e.g. to try again after `onError`. The image
|
|
674
|
+
* showing stays until the new one has loaded, and the events are sent as
|
|
675
|
+
* for a new source. An image in the cache loads from it.
|
|
676
|
+
*/
|
|
677
|
+
reload(): void;
|
|
678
|
+
};
|
|
665
679
|
/** A preloaded source that loaded (and is now cached). */
|
|
666
680
|
export interface PreloadSuccess {
|
|
667
681
|
/** The source's uri. */
|
|
@@ -760,14 +774,14 @@ export interface FastImageDefaults {
|
|
|
760
774
|
*/
|
|
761
775
|
transition?: boolean | number | Transition | null;
|
|
762
776
|
}
|
|
763
|
-
declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<
|
|
777
|
+
declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<FastImageRef>> & FastImageStaticProperties;
|
|
764
778
|
export interface FastImageBackgroundProps extends Omit<FastImageProps, 'style' | 'children'> {
|
|
765
779
|
/** The container's style; the image fills it. */
|
|
766
780
|
style?: StyleProp<ViewStyle>;
|
|
767
781
|
/** The image's style. */
|
|
768
782
|
imageStyle?: StyleProp<ImageStyle>;
|
|
769
783
|
/** A ref to the image (the FastImage inside). */
|
|
770
|
-
imageRef?: React.Ref<
|
|
784
|
+
imageRef?: React.Ref<FastImageRef>;
|
|
771
785
|
/** Content shown on top of the image. */
|
|
772
786
|
children?: React.ReactNode;
|
|
773
787
|
}
|