react-native-fast-image 10.6.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 +23 -2
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +6 -0
- package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +9 -0
- package/dist/composeRefs.d.ts +2 -0
- package/dist/index.cjs.js +27 -1
- package/dist/index.cjs.js.flow +2 -0
- package/dist/index.cjs.web.js +33 -8
- package/dist/index.cjs.web.js.flow +2 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +28 -2
- package/dist/index.js.flow +2 -0
- package/dist/index.web.d.ts +5 -3
- package/dist/index.web.js +34 -9
- package/dist/index.web.js.flow +2 -0
- package/dist/specs/FastImageViewNativeComponent.d.ts +5 -0
- package/ios/FastImage/FFFastImageView.h +4 -0
- package/ios/FastImage/FFFastImageView.m +5 -0
- package/ios/FastImage/FFFastImageViewComponentView.mm +11 -0
- package/package.json +1 -1
- package/src/composeRefs.ts +29 -0
- package/src/index.js.flow +2 -0
- package/src/index.tsx +35 -12
- package/src/index.web.tsx +57 -39
- package/src/specs/FastImageViewNativeComponent.ts +15 -3
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ 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
|
|
|
@@ -537,6 +537,27 @@ If supplied, changes the color of all the non-transparent pixels to the given co
|
|
|
537
537
|
|
|
538
538
|
<!-- api:props end -->
|
|
539
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
|
+
|
|
540
561
|
### `FastImageBackground`
|
|
541
562
|
|
|
542
563
|
An image with content on top of it: a view that the image fills, with the children on top.
|
|
@@ -577,7 +598,7 @@ The image's style.
|
|
|
577
598
|
|
|
578
599
|
#### `imageRef`
|
|
579
600
|
|
|
580
|
-
**Type:** `Ref<
|
|
601
|
+
**Type:** `Ref<FastImageRef>`
|
|
581
602
|
|
|
582
603
|
A ref to the image (the FastImage inside).
|
|
583
604
|
|
|
@@ -435,6 +435,12 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl>
|
|
|
435
435
|
view.setObjectFit(FastImageViewConverter.getScaleType(objectFit));
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
// The ref's reload() (a command).
|
|
439
|
+
@Override
|
|
440
|
+
public void reload(FastImageViewWithUrl view) {
|
|
441
|
+
view.reload();
|
|
442
|
+
}
|
|
443
|
+
|
|
438
444
|
@Override
|
|
439
445
|
public void onDropViewInstance(@NonNull FastImageViewWithUrl view) {
|
|
440
446
|
// Cancels its request, and nothing loads or tracks progress for it
|
|
@@ -364,6 +364,15 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
364
364
|
return true;
|
|
365
365
|
}
|
|
366
366
|
|
|
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;
|
|
372
|
+
mNeedsReload = true;
|
|
373
|
+
onAfterUpdate(mManager, requestManager);
|
|
374
|
+
}
|
|
375
|
+
|
|
367
376
|
// Shows at once, as a new source placeholder does (see updatePlaceholder),
|
|
368
377
|
// without loading the image again.
|
|
369
378
|
public void setPlaceholder(@Nullable Drawable placeholder) {
|
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,6 +280,27 @@ 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
|
|
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
|
+
};
|
|
302
|
+
}
|
|
303
|
+
//#endregion
|
|
282
304
|
//#region src/index.tsx
|
|
283
305
|
const resolvePlaceholder = (placeholder) => {
|
|
284
306
|
if (typeof placeholder === "string") {
|
|
@@ -357,9 +379,10 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
357
379
|
return Array.isArray(single) ? single.map((entry) => react_native.Image.resolveAssetSource(entry)) : react_native.Image.resolveAssetSource(single);
|
|
358
380
|
}, [source]);
|
|
359
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]);
|
|
360
383
|
return /* @__PURE__ */ react.default.createElement(FastImageViewNativeComponent_default, {
|
|
361
384
|
...props,
|
|
362
|
-
ref
|
|
385
|
+
ref,
|
|
363
386
|
style: [styles.image, style],
|
|
364
387
|
recyclingKey: (_props$recyclingKey = props.recyclingKey) !== null && _props$recyclingKey !== void 0 ? _props$recyclingKey : void 0,
|
|
365
388
|
loopCount: loopCount(loop),
|
|
@@ -386,6 +409,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
386
409
|
objectPositionYOffset: position.y.offset
|
|
387
410
|
});
|
|
388
411
|
}
|
|
412
|
+
function addReload(view) {
|
|
413
|
+
if (view) view.reload = () => Commands.reload(view);
|
|
414
|
+
}
|
|
389
415
|
const FastImageMemo = (0, react.memo)(FastImageBase);
|
|
390
416
|
const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
|
|
391
417
|
forwardedRef: ref,
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -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,10 +319,15 @@ 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
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)();
|
|
@@ -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,
|
|
@@ -604,11 +630,10 @@ FastImage.setDefaults = () => {};
|
|
|
604
630
|
FastImage.writeToCache = () => Promise.resolve(notSupported);
|
|
605
631
|
FastImage.getCachePath = () => Promise.resolve(notSupported);
|
|
606
632
|
const styles = react_native.StyleSheet.create({ container: { overflow: "hidden" } });
|
|
607
|
-
const FastImageWithRef = FastImageComponent;
|
|
608
633
|
const FastImageBackground = (0, react.forwardRef)(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
609
634
|
style,
|
|
610
635
|
ref
|
|
611
|
-
}, /* @__PURE__ */ react.default.createElement(
|
|
636
|
+
}, /* @__PURE__ */ react.default.createElement(FastImageComponent, {
|
|
612
637
|
...props,
|
|
613
638
|
style: [react_native.StyleSheet.absoluteFill, imageStyle],
|
|
614
639
|
ref: imageRef
|
|
@@ -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
|
@@ -664,6 +664,18 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
664
664
|
children?: "FastImage doesn't take children: use FastImageBackground for content over an image";
|
|
665
665
|
}
|
|
666
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
|
+
};
|
|
667
679
|
/** A preloaded source that loaded (and is now cached). */
|
|
668
680
|
export interface PreloadSuccess {
|
|
669
681
|
/** The source's uri. */
|
|
@@ -762,14 +774,14 @@ export interface FastImageDefaults {
|
|
|
762
774
|
*/
|
|
763
775
|
transition?: boolean | number | Transition | null;
|
|
764
776
|
}
|
|
765
|
-
declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<
|
|
777
|
+
declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<FastImageRef>> & FastImageStaticProperties;
|
|
766
778
|
export interface FastImageBackgroundProps extends Omit<FastImageProps, 'style' | 'children'> {
|
|
767
779
|
/** The container's style; the image fills it. */
|
|
768
780
|
style?: StyleProp<ViewStyle>;
|
|
769
781
|
/** The image's style. */
|
|
770
782
|
imageStyle?: StyleProp<ImageStyle>;
|
|
771
783
|
/** A ref to the image (the FastImage inside). */
|
|
772
|
-
imageRef?: React.Ref<
|
|
784
|
+
imageRef?: React.Ref<FastImageRef>;
|
|
773
785
|
/** Content shown on top of the image. */
|
|
774
786
|
children?: React.ReactNode;
|
|
775
787
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { Children, forwardRef, memo, useMemo } from "react";
|
|
2
|
-
import { Image, Platform, StyleSheet, TurboModuleRegistry, View, codegenNativeComponent } from "react-native";
|
|
2
|
+
import { Image, Platform, StyleSheet, TurboModuleRegistry, View, codegenNativeCommands, codegenNativeComponent } from "react-native";
|
|
3
3
|
//#region src/constants.ts
|
|
4
4
|
const resizeMode = {
|
|
5
5
|
contain: "contain",
|
|
@@ -228,6 +228,7 @@ function objectPositionOrCentered(position) {
|
|
|
228
228
|
}
|
|
229
229
|
//#endregion
|
|
230
230
|
//#region src/specs/FastImageViewNativeComponent.ts
|
|
231
|
+
const Commands = codegenNativeCommands({ supportedCommands: ["reload"] });
|
|
231
232
|
var FastImageViewNativeComponent_default = codegenNativeComponent("FastImageView");
|
|
232
233
|
//#endregion
|
|
233
234
|
//#region src/specs/NativeFastImageModule.ts
|
|
@@ -252,6 +253,27 @@ function dataUri(value) {
|
|
|
252
253
|
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");
|
|
253
254
|
}
|
|
254
255
|
//#endregion
|
|
256
|
+
//#region src/composeRefs.ts
|
|
257
|
+
function composeRefs(...refs) {
|
|
258
|
+
return (value) => {
|
|
259
|
+
const cleanups = refs.map((ref) => {
|
|
260
|
+
if (typeof ref === "function") {
|
|
261
|
+
const cleanup = ref(value);
|
|
262
|
+
return typeof cleanup === "function" ? cleanup : () => {
|
|
263
|
+
ref(null);
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (ref) {
|
|
267
|
+
ref.current = value;
|
|
268
|
+
return () => {
|
|
269
|
+
ref.current = null;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
return () => cleanups.forEach((cleanup) => cleanup === null || cleanup === void 0 ? void 0 : cleanup());
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
255
277
|
//#region src/index.tsx
|
|
256
278
|
const resolvePlaceholder = (placeholder) => {
|
|
257
279
|
if (typeof placeholder === "string") {
|
|
@@ -330,9 +352,10 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
330
352
|
return Array.isArray(single) ? single.map((entry) => Image.resolveAssetSource(entry)) : Image.resolveAssetSource(single);
|
|
331
353
|
}, [source]);
|
|
332
354
|
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);
|
|
355
|
+
const ref = useMemo(() => composeRefs(addReload, forwardedRef), [forwardedRef]);
|
|
333
356
|
return /* @__PURE__ */ React.createElement(FastImageViewNativeComponent_default, {
|
|
334
357
|
...props,
|
|
335
|
-
ref
|
|
358
|
+
ref,
|
|
336
359
|
style: [styles.image, style],
|
|
337
360
|
recyclingKey: (_props$recyclingKey = props.recyclingKey) !== null && _props$recyclingKey !== void 0 ? _props$recyclingKey : void 0,
|
|
338
361
|
loopCount: loopCount(loop),
|
|
@@ -359,6 +382,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
359
382
|
objectPositionYOffset: position.y.offset
|
|
360
383
|
});
|
|
361
384
|
}
|
|
385
|
+
function addReload(view) {
|
|
386
|
+
if (view) view.reload = () => Commands.reload(view);
|
|
387
|
+
}
|
|
362
388
|
const FastImageMemo = memo(FastImageBase);
|
|
363
389
|
const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.createElement(FastImageMemo, {
|
|
364
390
|
forwardedRef: ref,
|
package/dist/index.js.flow
CHANGED
|
@@ -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.web.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import type { FastImageBackgroundProps, FastImageProps, FastImageRef, FastImageStaticProperties } from './index';
|
|
3
4
|
export type * from './index';
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
type ViewRef = React.ComponentRef<typeof View>;
|
|
6
|
+
declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<FastImageRef>> & FastImageStaticProperties;
|
|
7
|
+
export declare const FastImageBackground: React.ForwardRefExoticComponent<FastImageBackgroundProps & React.RefAttributes<ViewRef>>;
|
|
6
8
|
export default FastImage;
|
package/dist/index.web.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Children, forwardRef, memo, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
1
|
+
import React, { Children, forwardRef, memo, useEffect, useId, useLayoutEffect, useMemo, 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
|
|
@@ -255,6 +255,27 @@ function dataUri(value) {
|
|
|
255
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");
|
|
256
256
|
}
|
|
257
257
|
//#endregion
|
|
258
|
+
//#region src/composeRefs.ts
|
|
259
|
+
function composeRefs(...refs) {
|
|
260
|
+
return (value) => {
|
|
261
|
+
const cleanups = refs.map((ref) => {
|
|
262
|
+
if (typeof ref === "function") {
|
|
263
|
+
const cleanup = ref(value);
|
|
264
|
+
return typeof cleanup === "function" ? cleanup : () => {
|
|
265
|
+
ref(null);
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (ref) {
|
|
269
|
+
ref.current = value;
|
|
270
|
+
return () => {
|
|
271
|
+
ref.current = null;
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
return () => cleanups.forEach((cleanup) => cleanup === null || cleanup === void 0 ? void 0 : cleanup());
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
258
279
|
//#region src/index.web.tsx
|
|
259
280
|
const notSupported = {
|
|
260
281
|
ok: false,
|
|
@@ -271,10 +292,15 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
271
292
|
const src = sized ? sized.src : resolveUri(source);
|
|
272
293
|
const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
|
|
273
294
|
const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
|
|
295
|
+
const [reloads, setReloads] = useState(0);
|
|
296
|
+
const ref = useMemo(() => composeRefs((view) => {
|
|
297
|
+
if (view) view.reload = () => setReloads((count) => count + 1);
|
|
298
|
+
}, forwardedRef), [forwardedRef]);
|
|
274
299
|
const priority = sourcePriority(source);
|
|
275
300
|
const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
|
|
276
301
|
const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
|
|
277
302
|
const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
|
|
303
|
+
const placeholderKey = `${urlPlaceholder} ${reloads}`;
|
|
278
304
|
const [failedPlaceholder, setFailedPlaceholder] = useState();
|
|
279
305
|
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : urlPlaceholder ? void 0 : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
280
306
|
const [result, setResult] = useState();
|
|
@@ -288,14 +314,14 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
288
314
|
const showDefault = ok !== true;
|
|
289
315
|
useClientLayoutEffect(() => {
|
|
290
316
|
if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
|
|
291
|
-
}, [key]);
|
|
317
|
+
}, [key, reloads]);
|
|
292
318
|
return /* @__PURE__ */ React.createElement(View, {
|
|
293
319
|
...viewProps,
|
|
294
320
|
"aria-label": label,
|
|
295
321
|
style: [styles.container, style],
|
|
296
322
|
onLayout,
|
|
297
323
|
pointerEvents,
|
|
298
|
-
ref
|
|
324
|
+
ref
|
|
299
325
|
}, defaultUri && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
|
|
300
326
|
key: `default ${defaultUri}`,
|
|
301
327
|
src: defaultUri,
|
|
@@ -307,7 +333,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
307
333
|
filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
|
|
308
334
|
alt: key ? "" : label !== null && label !== void 0 ? label : ""
|
|
309
335
|
}) : null, urlPlaceholder && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
|
|
310
|
-
key: `placeholder ${
|
|
336
|
+
key: `placeholder ${placeholderKey}`,
|
|
311
337
|
src: urlPlaceholder,
|
|
312
338
|
loading,
|
|
313
339
|
priority: "high",
|
|
@@ -315,11 +341,11 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
315
341
|
fit: fit === "scale-down" || fit === "none" ? "contain" : fit,
|
|
316
342
|
position,
|
|
317
343
|
filter: tintColor ? `url(#${tintId})` : void 0,
|
|
318
|
-
hidden: failedPlaceholder ===
|
|
319
|
-
onError: () => setFailedPlaceholder(
|
|
344
|
+
hidden: failedPlaceholder === placeholderKey,
|
|
345
|
+
onError: () => setFailedPlaceholder(placeholderKey),
|
|
320
346
|
alt: ""
|
|
321
347
|
}) : null, key && src ? /* @__PURE__ */ React.createElement(Picture, {
|
|
322
|
-
key: several ? "sizes" : key
|
|
348
|
+
key: `${several ? "sizes" : key} ${reloads}`,
|
|
323
349
|
src,
|
|
324
350
|
srcSet,
|
|
325
351
|
sizes,
|
|
@@ -577,11 +603,10 @@ FastImage.setDefaults = () => {};
|
|
|
577
603
|
FastImage.writeToCache = () => Promise.resolve(notSupported);
|
|
578
604
|
FastImage.getCachePath = () => Promise.resolve(notSupported);
|
|
579
605
|
const styles = StyleSheet.create({ container: { overflow: "hidden" } });
|
|
580
|
-
const FastImageWithRef = FastImageComponent;
|
|
581
606
|
const FastImageBackground = forwardRef(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ React.createElement(View, {
|
|
582
607
|
style,
|
|
583
608
|
ref
|
|
584
|
-
}, /* @__PURE__ */ React.createElement(
|
|
609
|
+
}, /* @__PURE__ */ React.createElement(FastImageComponent, {
|
|
585
610
|
...props,
|
|
586
611
|
style: [StyleSheet.absoluteFill, imageStyle],
|
|
587
612
|
ref: imageRef
|
package/dist/index.web.js.flow
CHANGED
|
@@ -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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
1
2
|
import type { CodegenTypes, ColorValue, HostComponent, ViewProps } from 'react-native';
|
|
2
3
|
type LoadStartEvent = Readonly<{}>;
|
|
3
4
|
type ProgressEvent = Readonly<{
|
|
@@ -44,5 +45,9 @@ export interface NativeProps extends ViewProps {
|
|
|
44
45
|
onFastImageError?: CodegenTypes.DirectEventHandler<ErrorEvent>;
|
|
45
46
|
onFastImageLoadEnd?: CodegenTypes.DirectEventHandler<LoadEndEvent>;
|
|
46
47
|
}
|
|
48
|
+
interface NativeCommands {
|
|
49
|
+
reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
|
|
50
|
+
}
|
|
51
|
+
export declare const Commands: NativeCommands;
|
|
47
52
|
declare const _default: HostComponent<NativeProps>;
|
|
48
53
|
export default _default;
|
|
@@ -82,6 +82,10 @@ typedef NS_ENUM(NSInteger, FFFObjectFit) {
|
|
|
82
82
|
// the view has a size, when it needs one).
|
|
83
83
|
- (void) didSetProps;
|
|
84
84
|
|
|
85
|
+
// Loads the image again, as a new source would (once the view has a size,
|
|
86
|
+
// when it needs one): the ref's reload().
|
|
87
|
+
- (void) reload;
|
|
88
|
+
|
|
85
89
|
// Keeps the view's mask (React Native's, for its rounded corners) on the box
|
|
86
90
|
// the view was laid out at, while objectPosition moves the view: after React
|
|
87
91
|
// Native sets or removes it.
|
|
@@ -1047,6 +1047,11 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1047
1047
|
}
|
|
1048
1048
|
}
|
|
1049
1049
|
|
|
1050
|
+
- (void) reload {
|
|
1051
|
+
_needsReload = YES;
|
|
1052
|
+
[self didSetProps];
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1050
1055
|
// The same image decoded again for a dynamicRangeLimit between HDR and SDR,
|
|
1051
1056
|
// as a resize is (reloadIfResized): without events once it has loaded, and
|
|
1052
1057
|
// the image showing stays if the load fails. A display setting shouldn't
|
|
@@ -388,6 +388,17 @@ typedef NS_OPTIONS(int32_t, FFFEvent) {
|
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
// The view's commands (reload), from the ref's methods.
|
|
392
|
+
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
|
|
393
|
+
{
|
|
394
|
+
RCTFastImageViewHandleCommand(self, commandName, args);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
- (void)reload
|
|
398
|
+
{
|
|
399
|
+
[_imageView reload];
|
|
400
|
+
}
|
|
401
|
+
|
|
391
402
|
// React Native makes the image view's mask again when the appearance changes
|
|
392
403
|
// (as after an update): where objectPosition moved the view, it moves back.
|
|
393
404
|
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
|
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Shared by the native (index.tsx) and web (index.web.tsx) versions.
|
|
2
|
+
import type React from 'react'
|
|
3
|
+
|
|
4
|
+
// One ref that sets each of these refs (as React Native's useMergeRefs does),
|
|
5
|
+
// and clears them when it's cleared.
|
|
6
|
+
export function composeRefs<T>(
|
|
7
|
+
...refs: (React.Ref<T> | undefined)[]
|
|
8
|
+
): React.RefCallback<T> {
|
|
9
|
+
return (value) => {
|
|
10
|
+
const cleanups = refs.map((ref) => {
|
|
11
|
+
if (typeof ref === 'function') {
|
|
12
|
+
const cleanup = ref(value)
|
|
13
|
+
return typeof cleanup === 'function'
|
|
14
|
+
? cleanup
|
|
15
|
+
: () => {
|
|
16
|
+
ref(null)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (ref) {
|
|
20
|
+
ref.current = value
|
|
21
|
+
return () => {
|
|
22
|
+
ref.current = null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return undefined
|
|
26
|
+
})
|
|
27
|
+
return () => cleanups.forEach((cleanup) => cleanup?.())
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/index.js.flow
CHANGED
|
@@ -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/src/index.tsx
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { cacheControl, priority, resizeMode } from './constants'
|
|
15
15
|
import { fromStyle, resolveObjectFit } from './objectFit'
|
|
16
16
|
import { objectPositionOrCentered, type ObjectPosition } from './objectPosition'
|
|
17
|
-
import FastImageView from './specs/FastImageViewNativeComponent'
|
|
17
|
+
import FastImageView, { Commands } from './specs/FastImageViewNativeComponent'
|
|
18
18
|
import NativeFastImageModule from './specs/NativeFastImageModule'
|
|
19
19
|
import {
|
|
20
20
|
dataUri,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
isImageName,
|
|
23
23
|
placeholderSource,
|
|
24
24
|
} from './placeholders'
|
|
25
|
+
import { composeRefs } from './composeRefs'
|
|
25
26
|
import { warnIfChildren, warnOnce } from './warnings'
|
|
26
27
|
|
|
27
28
|
/** How the image fits the view: see [`objectFit`](#objectfit). */
|
|
@@ -920,11 +921,15 @@ function FastImageBase({
|
|
|
920
921
|
: resolvePlaceholder(defaultSource)
|
|
921
922
|
: (placeholderSource(placeholder) ??
|
|
922
923
|
resolvePlaceholder(placeholder ?? defaultSource))
|
|
924
|
+
const ref = useMemo(
|
|
925
|
+
() => composeRefs<FastImageRef>(addReload, forwardedRef),
|
|
926
|
+
[forwardedRef],
|
|
927
|
+
)
|
|
923
928
|
|
|
924
929
|
return (
|
|
925
930
|
<FastImageView
|
|
926
931
|
{...props}
|
|
927
|
-
ref={
|
|
932
|
+
ref={ref}
|
|
928
933
|
// Clips the image to borderRadius by default, as React Native's
|
|
929
934
|
// Image does.
|
|
930
935
|
style={[styles.image, style]}
|
|
@@ -967,19 +972,37 @@ function FastImageBase({
|
|
|
967
972
|
)
|
|
968
973
|
}
|
|
969
974
|
|
|
975
|
+
// The ref is the native view, with reload() added, as React Native's
|
|
976
|
+
// TextInput adds its methods to its native view.
|
|
977
|
+
function addReload(view: FastImageRef | null) {
|
|
978
|
+
if (view) view.reload = () => Commands.reload(view as any)
|
|
979
|
+
}
|
|
980
|
+
|
|
970
981
|
const FastImageMemo = memo(FastImageBase)
|
|
971
982
|
|
|
972
|
-
// What a ref to
|
|
973
|
-
//
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
//
|
|
977
|
-
// a ref.
|
|
983
|
+
// What a ref to FastImageBackground gets (its view), and, with reload(), one
|
|
984
|
+
// to FastImage (its native view, which has the same methods, e.g. measure).
|
|
985
|
+
// ComponentRef, as React Native's types declare View as a class, and its
|
|
986
|
+
// Strict TypeScript API (opt-in from 0.80, the default from 0.87) as a
|
|
987
|
+
// function component that takes a ref.
|
|
978
988
|
type ViewRef = React.ComponentRef<typeof View>
|
|
979
989
|
|
|
990
|
+
/**
|
|
991
|
+
* What a ref to `FastImage` gets: its native view, with a View's methods
|
|
992
|
+
* (e.g. `measure`), and `reload()`. E.g. `useRef<FastImageRef>(null)`.
|
|
993
|
+
*/
|
|
994
|
+
export type FastImageRef = ViewRef & {
|
|
995
|
+
/**
|
|
996
|
+
* Loads the image again, e.g. to try again after `onError`. The image
|
|
997
|
+
* showing stays until the new one has loaded, and the events are sent as
|
|
998
|
+
* for a new source. An image in the cache loads from it.
|
|
999
|
+
*/
|
|
1000
|
+
reload(): void
|
|
1001
|
+
}
|
|
1002
|
+
|
|
980
1003
|
const FastImageComponent: React.ForwardRefExoticComponent<
|
|
981
|
-
FastImageProps & React.RefAttributes<
|
|
982
|
-
> = forwardRef((props: FastImageProps, ref: React.Ref<
|
|
1004
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
1005
|
+
> = forwardRef((props: FastImageProps, ref: React.Ref<FastImageRef>) => (
|
|
983
1006
|
<FastImageMemo forwardedRef={ref} {...props} />
|
|
984
1007
|
))
|
|
985
1008
|
|
|
@@ -1112,7 +1135,7 @@ export interface FastImageDefaults {
|
|
|
1112
1135
|
}
|
|
1113
1136
|
|
|
1114
1137
|
const FastImage: React.ForwardRefExoticComponent<
|
|
1115
|
-
FastImageProps & React.RefAttributes<
|
|
1138
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
1116
1139
|
> &
|
|
1117
1140
|
FastImageStaticProperties = FastImageComponent as any
|
|
1118
1141
|
|
|
@@ -1223,7 +1246,7 @@ export interface FastImageBackgroundProps extends Omit<
|
|
|
1223
1246
|
/** The image's style. */
|
|
1224
1247
|
imageStyle?: StyleProp<ImageStyle>
|
|
1225
1248
|
/** A ref to the image (the FastImage inside). */
|
|
1226
|
-
imageRef?: React.Ref<
|
|
1249
|
+
imageRef?: React.Ref<FastImageRef>
|
|
1227
1250
|
/** Content shown on top of the image. */
|
|
1228
1251
|
children?: React.ReactNode
|
|
1229
1252
|
}
|
package/src/index.web.tsx
CHANGED
|
@@ -23,6 +23,7 @@ import React, {
|
|
|
23
23
|
useEffect,
|
|
24
24
|
useId,
|
|
25
25
|
useLayoutEffect,
|
|
26
|
+
useMemo,
|
|
26
27
|
useRef,
|
|
27
28
|
useState,
|
|
28
29
|
} from 'react'
|
|
@@ -37,12 +38,14 @@ import {
|
|
|
37
38
|
hasSourcePlaceholder,
|
|
38
39
|
placeholderSource,
|
|
39
40
|
} from './placeholders'
|
|
41
|
+
import { composeRefs } from './composeRefs'
|
|
40
42
|
import { warnIfChildren, warnOnce } from './warnings'
|
|
41
43
|
import type {
|
|
42
44
|
CachePathResult,
|
|
43
45
|
CacheState,
|
|
44
46
|
FastImageBackgroundProps,
|
|
45
47
|
FastImageProps,
|
|
48
|
+
FastImageRef,
|
|
46
49
|
FastImageStaticProperties,
|
|
47
50
|
ObjectFit,
|
|
48
51
|
PreloadResult,
|
|
@@ -126,6 +129,17 @@ function FastImageBase({
|
|
|
126
129
|
// one doesn't stay while it loads; with several sizes, the <img> stays,
|
|
127
130
|
// and the browser shows the previous image until the new one has loaded.
|
|
128
131
|
const key = srcSet ?? src
|
|
132
|
+
// Counts the ref's reload() calls: each gives the image a new <img>, which
|
|
133
|
+
// the browser loads again (as a new view would). The image showing stays
|
|
134
|
+
// (it has loaded), or stays hidden (it failed), until then.
|
|
135
|
+
const [reloads, setReloads] = useState(0)
|
|
136
|
+
const ref = useMemo(
|
|
137
|
+
() =>
|
|
138
|
+
composeRefs((view: FastImageRef | null) => {
|
|
139
|
+
if (view) view.reload = () => setReloads((count) => count + 1)
|
|
140
|
+
}, forwardedRef),
|
|
141
|
+
[forwardedRef],
|
|
142
|
+
)
|
|
129
143
|
const priority = sourcePriority(source)
|
|
130
144
|
// The source's blurDataUri, or its ThumbHash made into one (with
|
|
131
145
|
// `react-native-fast-image/thumbhash`), shows blurred (see PreviewBlur)
|
|
@@ -140,6 +154,8 @@ function FastImageBase({
|
|
|
140
154
|
// placeholders as it loads, as on iOS and Android. The browser loads it
|
|
141
155
|
// like any image: it can't look in its cache only (cache: 'cacheOnly').
|
|
142
156
|
const urlPlaceholder = placeholderSource(placeholder)?.uri
|
|
157
|
+
// reload() gives it a new <img> too, so one that failed is asked for again.
|
|
158
|
+
const placeholderKey = `${urlPlaceholder} ${reloads}`
|
|
143
159
|
const [failedPlaceholder, setFailedPlaceholder] = useState<string>()
|
|
144
160
|
// defaultSource only shows without `placeholder`, as on iOS and Android,
|
|
145
161
|
// or in place of a thumbhash or blurDataUri the web can't show (one of
|
|
@@ -179,7 +195,7 @@ function FastImageBase({
|
|
|
179
195
|
if (key) onLoadStart?.()
|
|
180
196
|
// Once per image the browser is asked for.
|
|
181
197
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
182
|
-
}, [key])
|
|
198
|
+
}, [key, reloads])
|
|
183
199
|
|
|
184
200
|
return (
|
|
185
201
|
<View
|
|
@@ -188,7 +204,7 @@ function FastImageBase({
|
|
|
188
204
|
style={[styles.container, style]}
|
|
189
205
|
onLayout={onLayout}
|
|
190
206
|
pointerEvents={pointerEvents}
|
|
191
|
-
ref={
|
|
207
|
+
ref={ref}
|
|
192
208
|
>
|
|
193
209
|
{defaultUri && showDefault ? (
|
|
194
210
|
<Picture
|
|
@@ -226,7 +242,7 @@ function FastImageBase({
|
|
|
226
242
|
) : null}
|
|
227
243
|
{urlPlaceholder && showDefault ? (
|
|
228
244
|
<Picture
|
|
229
|
-
key={`placeholder ${
|
|
245
|
+
key={`placeholder ${placeholderKey}`}
|
|
230
246
|
src={urlPlaceholder}
|
|
231
247
|
loading={loading}
|
|
232
248
|
// It only helps if it comes before the image, as on iOS
|
|
@@ -241,14 +257,14 @@ function FastImageBase({
|
|
|
241
257
|
// Tinted, not blurred, as on iOS and Android.
|
|
242
258
|
filter={tintColor ? `url(#${tintId})` : undefined}
|
|
243
259
|
// Nothing (no broken image icon) if it fails.
|
|
244
|
-
hidden={failedPlaceholder ===
|
|
245
|
-
onError={() => setFailedPlaceholder(
|
|
260
|
+
hidden={failedPlaceholder === placeholderKey}
|
|
261
|
+
onError={() => setFailedPlaceholder(placeholderKey)}
|
|
246
262
|
alt=""
|
|
247
263
|
/>
|
|
248
264
|
) : null}
|
|
249
265
|
{key && src ? (
|
|
250
266
|
<Picture
|
|
251
|
-
key={several ? 'sizes' : key}
|
|
267
|
+
key={`${several ? 'sizes' : key} ${reloads}`}
|
|
252
268
|
src={src}
|
|
253
269
|
srcSet={srcSet}
|
|
254
270
|
sizes={sizes}
|
|
@@ -653,11 +669,15 @@ const tintStyle = {
|
|
|
653
669
|
|
|
654
670
|
const FastImageMemo = memo(FastImageBase)
|
|
655
671
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
672
|
+
// What a ref to FastImageBackground gets, as in index.tsx: a View's
|
|
673
|
+
// (react-native-web's element). FastImage's has reload() too (FastImageRef).
|
|
674
|
+
type ViewRef = React.ComponentRef<typeof View>
|
|
675
|
+
|
|
676
|
+
const FastImageComponent: React.ForwardRefExoticComponent<
|
|
677
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
678
|
+
> = forwardRef((props: FastImageProps, ref: React.Ref<FastImageRef>) => (
|
|
679
|
+
<FastImageMemo forwardedRef={ref} {...props} />
|
|
680
|
+
))
|
|
661
681
|
|
|
662
682
|
FastImageComponent.displayName = 'FastImage'
|
|
663
683
|
|
|
@@ -672,7 +692,9 @@ function getSize(uri: string) {
|
|
|
672
692
|
})
|
|
673
693
|
}
|
|
674
694
|
|
|
675
|
-
const FastImage: React.
|
|
695
|
+
const FastImage: React.ForwardRefExoticComponent<
|
|
696
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
697
|
+
> &
|
|
676
698
|
FastImageStaticProperties = FastImageComponent as any
|
|
677
699
|
|
|
678
700
|
FastImage.cacheControl = cacheControl
|
|
@@ -720,33 +742,29 @@ const styles = StyleSheet.create({
|
|
|
720
742
|
},
|
|
721
743
|
})
|
|
722
744
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
{children}
|
|
747
|
-
</View>
|
|
748
|
-
),
|
|
749
|
-
)
|
|
745
|
+
export const FastImageBackground: React.ForwardRefExoticComponent<
|
|
746
|
+
FastImageBackgroundProps & React.RefAttributes<ViewRef>
|
|
747
|
+
> = forwardRef(
|
|
748
|
+
(
|
|
749
|
+
{
|
|
750
|
+
style,
|
|
751
|
+
imageStyle,
|
|
752
|
+
imageRef,
|
|
753
|
+
children,
|
|
754
|
+
...props
|
|
755
|
+
}: FastImageBackgroundProps,
|
|
756
|
+
ref: React.Ref<ViewRef>,
|
|
757
|
+
) => (
|
|
758
|
+
<View style={style} ref={ref}>
|
|
759
|
+
<FastImageComponent
|
|
760
|
+
{...props}
|
|
761
|
+
style={[StyleSheet.absoluteFill, imageStyle]}
|
|
762
|
+
ref={imageRef}
|
|
763
|
+
/>
|
|
764
|
+
{children}
|
|
765
|
+
</View>
|
|
766
|
+
),
|
|
767
|
+
)
|
|
750
768
|
|
|
751
769
|
FastImageBackground.displayName = 'FastImageBackground'
|
|
752
770
|
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
// The native image view, from React Native's Codegen (the New Architecture's
|
|
2
2
|
// component). FastImage (index.tsx) renders it as its only view, and turns
|
|
3
3
|
// its props into these.
|
|
4
|
+
import type * as React from 'react'
|
|
4
5
|
import type {
|
|
5
6
|
CodegenTypes,
|
|
6
7
|
ColorValue,
|
|
7
8
|
HostComponent,
|
|
8
9
|
ViewProps,
|
|
9
10
|
} from 'react-native'
|
|
10
|
-
// React Native's Babel plugin replaces the
|
|
11
|
-
// view config, so the
|
|
12
|
-
import { codegenNativeComponent } from 'react-native'
|
|
11
|
+
// React Native's Babel plugin replaces the calls below with the component's
|
|
12
|
+
// view config and commands, so the imports aren't used when the app runs.
|
|
13
|
+
import { codegenNativeCommands, codegenNativeComponent } from 'react-native'
|
|
13
14
|
|
|
14
15
|
type LoadStartEvent = Readonly<{}>
|
|
15
16
|
|
|
@@ -86,6 +87,17 @@ export interface NativeProps extends ViewProps {
|
|
|
86
87
|
onFastImageLoadEnd?: CodegenTypes.DirectEventHandler<LoadEndEvent>
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
interface NativeCommands {
|
|
91
|
+
// Loads the image again, as a new source would (with its events), keeping
|
|
92
|
+
// the image showing until then. ElementRef, which React Native 0.83's
|
|
93
|
+
// Codegen needs (later ones also take ComponentRef).
|
|
94
|
+
reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
98
|
+
supportedCommands: ['reload'],
|
|
99
|
+
})
|
|
100
|
+
|
|
89
101
|
export default codegenNativeComponent<NativeProps>(
|
|
90
102
|
'FastImageView',
|
|
91
103
|
) as HostComponent<NativeProps>
|