react-native-fast-image 10.6.0 → 10.8.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/FastImageViewConverter.java +10 -8
- 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 +36 -2
- package/dist/index.cjs.js.flow +12 -1
- package/dist/index.cjs.web.js +33 -8
- package/dist/index.cjs.web.js.flow +12 -1
- package/dist/index.d.ts +19 -3
- package/dist/index.js +37 -3
- package/dist/index.js.flow +12 -1
- package/dist/index.web.d.ts +5 -3
- package/dist/index.web.js +34 -9
- package/dist/index.web.js.flow +12 -1
- package/dist/reactNativeCache.d.ts +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/ios/FastImage/RCTConvert+FFFastImage.m +3 -0
- package/package.json +1 -1
- package/src/composeRefs.ts +29 -0
- package/src/index.js.flow +12 -1
- package/src/index.tsx +51 -14
- package/src/index.web.tsx +65 -42
- package/src/reactNativeCache.ts +28 -0
- 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
|
|
|
@@ -16,7 +16,6 @@ import com.bumptech.glide.load.model.LazyHeaders;
|
|
|
16
16
|
import com.bumptech.glide.request.RequestOptions;
|
|
17
17
|
import com.bumptech.glide.signature.ApplicationVersionSignature;
|
|
18
18
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
|
19
|
-
import com.facebook.react.bridge.NoSuchKeyException;
|
|
20
19
|
import com.facebook.react.bridge.ReadableMap;
|
|
21
20
|
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
|
22
21
|
import com.facebook.react.bridge.ReadableType;
|
|
@@ -34,6 +33,10 @@ class FastImageViewConverter {
|
|
|
34
33
|
put("immutable", FastImageCacheControl.IMMUTABLE);
|
|
35
34
|
put("web", FastImageCacheControl.WEB);
|
|
36
35
|
put("cacheOnly", FastImageCacheControl.CACHE_ONLY);
|
|
36
|
+
// React Native's ImageURISource value, which views take. Its
|
|
37
|
+
// others (e.g. 'reload') are immutable, as any value this map
|
|
38
|
+
// doesn't have (see getValueFromSource).
|
|
39
|
+
put("only-if-cached", FastImageCacheControl.CACHE_ONLY);
|
|
37
40
|
}};
|
|
38
41
|
|
|
39
42
|
private static final Map<String, Priority> FAST_IMAGE_PRIORITY_MAP =
|
|
@@ -189,13 +192,12 @@ class FastImageViewConverter {
|
|
|
189
192
|
return value;
|
|
190
193
|
}
|
|
191
194
|
|
|
195
|
+
// A value the map doesn't have (e.g. React Native's `cache: 'reload'`, or
|
|
196
|
+
// one that isn't a string) gets the default, as on iOS.
|
|
192
197
|
private static <T> T getValueFromSource(String propName, String defaultProp, Map<String, T> map, ReadableMap source) {
|
|
193
|
-
String
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
propValue = null;
|
|
198
|
-
}
|
|
199
|
-
return getValue(propName, defaultProp, map, propValue);
|
|
198
|
+
T value = source != null && source.hasKey(propName) && source.getType(propName) == ReadableType.String
|
|
199
|
+
? map.get(source.getString(propName))
|
|
200
|
+
: null;
|
|
201
|
+
return value != null ? value : map.get(defaultProp);
|
|
200
202
|
}
|
|
201
203
|
}
|
|
@@ -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,32 @@ 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
|
|
304
|
+
//#region src/reactNativeCache.ts
|
|
305
|
+
function warnIfReload(source) {
|
|
306
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && (source === null || source === void 0 ? void 0 : source.cache) === "reload") warnOnce("cache-reload", "react-native-fast-image: `cache: 'reload'` loads the image as the default, `'immutable'`: it isn't loaded again each time. For an image that changes at the same url, use `cache: 'web'` (it follows the server's cache headers), or change its `cacheKey`.", "warn");
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
282
309
|
//#region src/index.tsx
|
|
283
310
|
const resolvePlaceholder = (placeholder) => {
|
|
284
311
|
if (typeof placeholder === "string") {
|
|
@@ -352,14 +379,18 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
352
379
|
const fit = resolveObjectFit(objectFit, style);
|
|
353
380
|
const position = objectPositionOrCentered(objectPosition);
|
|
354
381
|
const resolvedSource = (0, react.useMemo)(() => {
|
|
355
|
-
for (const entry of [source].flat())
|
|
382
|
+
for (const entry of [source].flat()) {
|
|
383
|
+
dataUri(entry === null || entry === void 0 ? void 0 : entry.blurDataUri);
|
|
384
|
+
warnIfReload(entry);
|
|
385
|
+
}
|
|
356
386
|
const single = Array.isArray(source) && source.length === 1 ? source[0] : source;
|
|
357
387
|
return Array.isArray(single) ? single.map((entry) => react_native.Image.resolveAssetSource(entry)) : react_native.Image.resolveAssetSource(single);
|
|
358
388
|
}, [source]);
|
|
359
389
|
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);
|
|
390
|
+
const ref = (0, react.useMemo)(() => composeRefs(addReload, forwardedRef), [forwardedRef]);
|
|
360
391
|
return /* @__PURE__ */ react.default.createElement(FastImageViewNativeComponent_default, {
|
|
361
392
|
...props,
|
|
362
|
-
ref
|
|
393
|
+
ref,
|
|
363
394
|
style: [styles.image, style],
|
|
364
395
|
recyclingKey: (_props$recyclingKey = props.recyclingKey) !== null && _props$recyclingKey !== void 0 ? _props$recyclingKey : void 0,
|
|
365
396
|
loopCount: loopCount(loop),
|
|
@@ -386,6 +417,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
386
417
|
objectPositionYOffset: position.y.offset
|
|
387
418
|
});
|
|
388
419
|
}
|
|
420
|
+
function addReload(view) {
|
|
421
|
+
if (view) view.reload = () => Commands.reload(view);
|
|
422
|
+
}
|
|
389
423
|
const FastImageMemo = (0, react.memo)(FastImageBase);
|
|
390
424
|
const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
|
|
391
425
|
forwardedRef: ref,
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -137,6 +137,15 @@ export type FastImageSource = {
|
|
|
137
137
|
scale?: number,
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// React Native's ImageURISource cache values, which the views' `source` takes
|
|
141
|
+
// so a React Native source works as it is: native reads 'only-if-cached' as
|
|
142
|
+
// 'cacheOnly', and the others as the default, 'immutable'.
|
|
143
|
+
type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
|
|
144
|
+
type ViewSource = {
|
|
145
|
+
...FastImageSource,
|
|
146
|
+
cache?: CacheControls | ReactNativeCache,
|
|
147
|
+
}
|
|
148
|
+
|
|
140
149
|
export type FastImageProps = $ReadOnly<{|
|
|
141
150
|
...ViewProps,
|
|
142
151
|
onError?: ?(event: OnErrorEvent) => void,
|
|
@@ -145,7 +154,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
154
|
onLoadStart?: ?() => void,
|
|
146
155
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
156
|
|
|
148
|
-
source?: ?(
|
|
157
|
+
source?: ?(ViewSource | Array<ViewSource | number> | number),
|
|
149
158
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
159
|
// Deprecated: use placeholder, which is the same.
|
|
151
160
|
defaultSource?: ?number,
|
|
@@ -196,4 +205,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
196
205
|
source: FastImageSource,
|
|
197
206
|
file: string,
|
|
198
207
|
) => Promise<CachePathResult>;
|
|
208
|
+
// On a ref: loads the image again, e.g. to try again after onError.
|
|
209
|
+
reload(): void;
|
|
199
210
|
}
|
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
|
|
@@ -137,6 +137,15 @@ export type FastImageSource = {
|
|
|
137
137
|
scale?: number,
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// React Native's ImageURISource cache values, which the views' `source` takes
|
|
141
|
+
// so a React Native source works as it is: native reads 'only-if-cached' as
|
|
142
|
+
// 'cacheOnly', and the others as the default, 'immutable'.
|
|
143
|
+
type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
|
|
144
|
+
type ViewSource = {
|
|
145
|
+
...FastImageSource,
|
|
146
|
+
cache?: CacheControls | ReactNativeCache,
|
|
147
|
+
}
|
|
148
|
+
|
|
140
149
|
export type FastImageProps = $ReadOnly<{|
|
|
141
150
|
...ViewProps,
|
|
142
151
|
onError?: ?(event: OnErrorEvent) => void,
|
|
@@ -145,7 +154,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
154
|
onLoadStart?: ?() => void,
|
|
146
155
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
156
|
|
|
148
|
-
source?: ?(
|
|
157
|
+
source?: ?(ViewSource | Array<ViewSource | number> | number),
|
|
149
158
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
159
|
// Deprecated: use placeholder, which is the same.
|
|
151
160
|
defaultSource?: ?number,
|
|
@@ -196,4 +205,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
196
205
|
source: FastImageSource,
|
|
197
206
|
file: string,
|
|
198
207
|
) => Promise<CachePathResult>;
|
|
208
|
+
// On a ref: loads the image again, e.g. to try again after onError.
|
|
209
|
+
reload(): void;
|
|
199
210
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { ColorValue, View, StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
|
|
3
3
|
import { cacheControl, priority, resizeMode } from './constants';
|
|
4
4
|
import { type ObjectPosition } from './objectPosition';
|
|
5
|
+
import { type ReactNativeCache } from './reactNativeCache';
|
|
5
6
|
/** How the image fits the view: see [`objectFit`](#objectfit). */
|
|
6
7
|
export type ObjectFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down';
|
|
7
8
|
export type { ObjectPosition, PositionLength } from './objectPosition';
|
|
@@ -152,6 +153,9 @@ export type Source = {
|
|
|
152
153
|
/** With several sources, the scale `width` and `height` are multiplied by. */
|
|
153
154
|
scale?: number;
|
|
154
155
|
};
|
|
156
|
+
type ViewSource = Source | (Omit<Source, 'cache'> & {
|
|
157
|
+
cache?: ReactNativeCache;
|
|
158
|
+
});
|
|
155
159
|
/** A `thumbhash` or `blurDataUri` as `placeholder`. */
|
|
156
160
|
export type SourcePlaceholder = Pick<Source, 'thumbhash' | 'blurDataUri'>;
|
|
157
161
|
/** A url as `placeholder`, loaded like a source (see `placeholder`). */
|
|
@@ -334,7 +338,7 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
334
338
|
* - A `require()`d image can be one of the sizes: its size is the file's.
|
|
335
339
|
* - An array of one is the same as that source.
|
|
336
340
|
*/
|
|
337
|
-
source?:
|
|
341
|
+
source?: ViewSource | Array<ViewSource | ImageRequireSource> | ImageRequireSource;
|
|
338
342
|
/**
|
|
339
343
|
* A placeholder shown while the image loads, and if it fails to load:
|
|
340
344
|
*
|
|
@@ -664,6 +668,18 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
664
668
|
children?: "FastImage doesn't take children: use FastImageBackground for content over an image";
|
|
665
669
|
}
|
|
666
670
|
type ViewRef = React.ComponentRef<typeof View>;
|
|
671
|
+
/**
|
|
672
|
+
* What a ref to `FastImage` gets: its native view, with a View's methods
|
|
673
|
+
* (e.g. `measure`), and `reload()`. E.g. `useRef<FastImageRef>(null)`.
|
|
674
|
+
*/
|
|
675
|
+
export type FastImageRef = ViewRef & {
|
|
676
|
+
/**
|
|
677
|
+
* Loads the image again, e.g. to try again after `onError`. The image
|
|
678
|
+
* showing stays until the new one has loaded, and the events are sent as
|
|
679
|
+
* for a new source. An image in the cache loads from it.
|
|
680
|
+
*/
|
|
681
|
+
reload(): void;
|
|
682
|
+
};
|
|
667
683
|
/** A preloaded source that loaded (and is now cached). */
|
|
668
684
|
export interface PreloadSuccess {
|
|
669
685
|
/** The source's uri. */
|
|
@@ -762,14 +778,14 @@ export interface FastImageDefaults {
|
|
|
762
778
|
*/
|
|
763
779
|
transition?: boolean | number | Transition | null;
|
|
764
780
|
}
|
|
765
|
-
declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<
|
|
781
|
+
declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<FastImageRef>> & FastImageStaticProperties;
|
|
766
782
|
export interface FastImageBackgroundProps extends Omit<FastImageProps, 'style' | 'children'> {
|
|
767
783
|
/** The container's style; the image fills it. */
|
|
768
784
|
style?: StyleProp<ViewStyle>;
|
|
769
785
|
/** The image's style. */
|
|
770
786
|
imageStyle?: StyleProp<ImageStyle>;
|
|
771
787
|
/** A ref to the image (the FastImage inside). */
|
|
772
|
-
imageRef?: React.Ref<
|
|
788
|
+
imageRef?: React.Ref<FastImageRef>;
|
|
773
789
|
/** Content shown on top of the image. */
|
|
774
790
|
children?: React.ReactNode;
|
|
775
791
|
}
|
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,32 @@ 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
|
|
277
|
+
//#region src/reactNativeCache.ts
|
|
278
|
+
function warnIfReload(source) {
|
|
279
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && (source === null || source === void 0 ? void 0 : source.cache) === "reload") warnOnce("cache-reload", "react-native-fast-image: `cache: 'reload'` loads the image as the default, `'immutable'`: it isn't loaded again each time. For an image that changes at the same url, use `cache: 'web'` (it follows the server's cache headers), or change its `cacheKey`.", "warn");
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
255
282
|
//#region src/index.tsx
|
|
256
283
|
const resolvePlaceholder = (placeholder) => {
|
|
257
284
|
if (typeof placeholder === "string") {
|
|
@@ -325,14 +352,18 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
325
352
|
const fit = resolveObjectFit(objectFit, style);
|
|
326
353
|
const position = objectPositionOrCentered(objectPosition);
|
|
327
354
|
const resolvedSource = useMemo(() => {
|
|
328
|
-
for (const entry of [source].flat())
|
|
355
|
+
for (const entry of [source].flat()) {
|
|
356
|
+
dataUri(entry === null || entry === void 0 ? void 0 : entry.blurDataUri);
|
|
357
|
+
warnIfReload(entry);
|
|
358
|
+
}
|
|
329
359
|
const single = Array.isArray(source) && source.length === 1 ? source[0] : source;
|
|
330
360
|
return Array.isArray(single) ? single.map((entry) => Image.resolveAssetSource(entry)) : Image.resolveAssetSource(single);
|
|
331
361
|
}, [source]);
|
|
332
362
|
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);
|
|
363
|
+
const ref = useMemo(() => composeRefs(addReload, forwardedRef), [forwardedRef]);
|
|
333
364
|
return /* @__PURE__ */ React.createElement(FastImageViewNativeComponent_default, {
|
|
334
365
|
...props,
|
|
335
|
-
ref
|
|
366
|
+
ref,
|
|
336
367
|
style: [styles.image, style],
|
|
337
368
|
recyclingKey: (_props$recyclingKey = props.recyclingKey) !== null && _props$recyclingKey !== void 0 ? _props$recyclingKey : void 0,
|
|
338
369
|
loopCount: loopCount(loop),
|
|
@@ -359,6 +390,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
359
390
|
objectPositionYOffset: position.y.offset
|
|
360
391
|
});
|
|
361
392
|
}
|
|
393
|
+
function addReload(view) {
|
|
394
|
+
if (view) view.reload = () => Commands.reload(view);
|
|
395
|
+
}
|
|
362
396
|
const FastImageMemo = memo(FastImageBase);
|
|
363
397
|
const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.createElement(FastImageMemo, {
|
|
364
398
|
forwardedRef: ref,
|
package/dist/index.js.flow
CHANGED
|
@@ -137,6 +137,15 @@ export type FastImageSource = {
|
|
|
137
137
|
scale?: number,
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// React Native's ImageURISource cache values, which the views' `source` takes
|
|
141
|
+
// so a React Native source works as it is: native reads 'only-if-cached' as
|
|
142
|
+
// 'cacheOnly', and the others as the default, 'immutable'.
|
|
143
|
+
type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
|
|
144
|
+
type ViewSource = {
|
|
145
|
+
...FastImageSource,
|
|
146
|
+
cache?: CacheControls | ReactNativeCache,
|
|
147
|
+
}
|
|
148
|
+
|
|
140
149
|
export type FastImageProps = $ReadOnly<{|
|
|
141
150
|
...ViewProps,
|
|
142
151
|
onError?: ?(event: OnErrorEvent) => void,
|
|
@@ -145,7 +154,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
154
|
onLoadStart?: ?() => void,
|
|
146
155
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
156
|
|
|
148
|
-
source?: ?(
|
|
157
|
+
source?: ?(ViewSource | Array<ViewSource | number> | number),
|
|
149
158
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
159
|
// Deprecated: use placeholder, which is the same.
|
|
151
160
|
defaultSource?: ?number,
|
|
@@ -196,4 +205,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
196
205
|
source: FastImageSource,
|
|
197
206
|
file: string,
|
|
198
207
|
) => Promise<CachePathResult>;
|
|
208
|
+
// On a ref: loads the image again, e.g. to try again after onError.
|
|
209
|
+
reload(): void;
|
|
199
210
|
}
|
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
|
@@ -137,6 +137,15 @@ export type FastImageSource = {
|
|
|
137
137
|
scale?: number,
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// React Native's ImageURISource cache values, which the views' `source` takes
|
|
141
|
+
// so a React Native source works as it is: native reads 'only-if-cached' as
|
|
142
|
+
// 'cacheOnly', and the others as the default, 'immutable'.
|
|
143
|
+
type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
|
|
144
|
+
type ViewSource = {
|
|
145
|
+
...FastImageSource,
|
|
146
|
+
cache?: CacheControls | ReactNativeCache,
|
|
147
|
+
}
|
|
148
|
+
|
|
140
149
|
export type FastImageProps = $ReadOnly<{|
|
|
141
150
|
...ViewProps,
|
|
142
151
|
onError?: ?(event: OnErrorEvent) => void,
|
|
@@ -145,7 +154,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
154
|
onLoadStart?: ?() => void,
|
|
146
155
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
156
|
|
|
148
|
-
source?: ?(
|
|
157
|
+
source?: ?(ViewSource | Array<ViewSource | number> | number),
|
|
149
158
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
159
|
// Deprecated: use placeholder, which is the same.
|
|
151
160
|
defaultSource?: ?number,
|
|
@@ -196,4 +205,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
196
205
|
source: FastImageSource,
|
|
197
206
|
file: string,
|
|
198
207
|
) => Promise<CachePathResult>;
|
|
208
|
+
// On a ref: loads the image again, e.g. to try again after onError.
|
|
209
|
+
reload(): void;
|
|
199
210
|
}
|
|
@@ -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
|
|
@@ -21,6 +21,9 @@ RCT_ENUM_CONVERTER(FFFCacheControl, (@{
|
|
|
21
21
|
@"immutable": @(FFFCacheControlImmutable),
|
|
22
22
|
@"web": @(FFFCacheControlWeb),
|
|
23
23
|
@"cacheOnly": @(FFFCacheControlCacheOnly),
|
|
24
|
+
// React Native's ImageURISource value, which views
|
|
25
|
+
// take. Its others (e.g. 'reload') are the default.
|
|
26
|
+
@"only-if-cached": @(FFFCacheControlCacheOnly),
|
|
24
27
|
}), FFFCacheControlImmutable, integerValue);
|
|
25
28
|
|
|
26
29
|
+ (FFFastImageSource *)FFFastImageSource:(id)json {
|
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
|
@@ -137,6 +137,15 @@ export type FastImageSource = {
|
|
|
137
137
|
scale?: number,
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// React Native's ImageURISource cache values, which the views' `source` takes
|
|
141
|
+
// so a React Native source works as it is: native reads 'only-if-cached' as
|
|
142
|
+
// 'cacheOnly', and the others as the default, 'immutable'.
|
|
143
|
+
type ReactNativeCache = 'default' | 'reload' | 'force-cache' | 'only-if-cached'
|
|
144
|
+
type ViewSource = {
|
|
145
|
+
...FastImageSource,
|
|
146
|
+
cache?: CacheControls | ReactNativeCache,
|
|
147
|
+
}
|
|
148
|
+
|
|
140
149
|
export type FastImageProps = $ReadOnly<{|
|
|
141
150
|
...ViewProps,
|
|
142
151
|
onError?: ?(event: OnErrorEvent) => void,
|
|
@@ -145,7 +154,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
154
|
onLoadStart?: ?() => void,
|
|
146
155
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
156
|
|
|
148
|
-
source?: ?(
|
|
157
|
+
source?: ?(ViewSource | Array<ViewSource | number> | number),
|
|
149
158
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
159
|
// Deprecated: use placeholder, which is the same.
|
|
151
160
|
defaultSource?: ?number,
|
|
@@ -196,4 +205,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
196
205
|
source: FastImageSource,
|
|
197
206
|
file: string,
|
|
198
207
|
) => Promise<CachePathResult>;
|
|
208
|
+
// On a ref: loads the image again, e.g. to try again after onError.
|
|
209
|
+
reload(): void;
|
|
199
210
|
}
|
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,8 @@ import {
|
|
|
22
22
|
isImageName,
|
|
23
23
|
placeholderSource,
|
|
24
24
|
} from './placeholders'
|
|
25
|
+
import { composeRefs } from './composeRefs'
|
|
26
|
+
import { type ReactNativeCache, warnIfReload } from './reactNativeCache'
|
|
25
27
|
import { warnIfChildren, warnOnce } from './warnings'
|
|
26
28
|
|
|
27
29
|
/** How the image fits the view: see [`objectFit`](#objectfit). */
|
|
@@ -177,6 +179,14 @@ export type Source = {
|
|
|
177
179
|
scale?: number
|
|
178
180
|
}
|
|
179
181
|
|
|
182
|
+
// What the views' `source` takes: a Source, or one with React Native's cache
|
|
183
|
+
// values (see reactNativeCache.ts), so a React Native `ImageURISource` works as
|
|
184
|
+
// it is. Only the views: preload, getCachePath, writeToCache and placeholder
|
|
185
|
+
// take Fast Image's values.
|
|
186
|
+
type ViewSource =
|
|
187
|
+
| Source
|
|
188
|
+
| (Omit<Source, 'cache'> & { cache?: ReactNativeCache })
|
|
189
|
+
|
|
180
190
|
/** A `thumbhash` or `blurDataUri` as `placeholder`. */
|
|
181
191
|
export type SourcePlaceholder = Pick<Source, 'thumbhash' | 'blurDataUri'>
|
|
182
192
|
|
|
@@ -365,7 +375,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
365
375
|
* - A `require()`d image can be one of the sizes: its size is the file's.
|
|
366
376
|
* - An array of one is the same as that source.
|
|
367
377
|
*/
|
|
368
|
-
source?:
|
|
378
|
+
source?:
|
|
379
|
+
| ViewSource
|
|
380
|
+
| Array<ViewSource | ImageRequireSource>
|
|
381
|
+
| ImageRequireSource
|
|
369
382
|
/**
|
|
370
383
|
* A placeholder shown while the image loads, and if it fails to load:
|
|
371
384
|
*
|
|
@@ -900,9 +913,11 @@ function FastImageBase({
|
|
|
900
913
|
// only those change. Memoized on source, so an unchanged source reaches
|
|
901
914
|
// the native view as the same object, and React's prop diff stops at it.
|
|
902
915
|
const resolvedSource = useMemo(() => {
|
|
903
|
-
// A blurDataUri that isn't a data uri is ignored, with a warning
|
|
916
|
+
// A blurDataUri that isn't a data uri is ignored, with a warning, and
|
|
917
|
+
// React Native's cache: 'reload' warns (native reads its cache values).
|
|
904
918
|
for (const entry of [source].flat()) {
|
|
905
919
|
dataUri((entry as Source | undefined)?.blurDataUri)
|
|
920
|
+
warnIfReload(entry)
|
|
906
921
|
}
|
|
907
922
|
const single =
|
|
908
923
|
Array.isArray(source) && source.length === 1 ? source[0] : source
|
|
@@ -920,11 +935,15 @@ function FastImageBase({
|
|
|
920
935
|
: resolvePlaceholder(defaultSource)
|
|
921
936
|
: (placeholderSource(placeholder) ??
|
|
922
937
|
resolvePlaceholder(placeholder ?? defaultSource))
|
|
938
|
+
const ref = useMemo(
|
|
939
|
+
() => composeRefs<FastImageRef>(addReload, forwardedRef),
|
|
940
|
+
[forwardedRef],
|
|
941
|
+
)
|
|
923
942
|
|
|
924
943
|
return (
|
|
925
944
|
<FastImageView
|
|
926
945
|
{...props}
|
|
927
|
-
ref={
|
|
946
|
+
ref={ref}
|
|
928
947
|
// Clips the image to borderRadius by default, as React Native's
|
|
929
948
|
// Image does.
|
|
930
949
|
style={[styles.image, style]}
|
|
@@ -967,19 +986,37 @@ function FastImageBase({
|
|
|
967
986
|
)
|
|
968
987
|
}
|
|
969
988
|
|
|
989
|
+
// The ref is the native view, with reload() added, as React Native's
|
|
990
|
+
// TextInput adds its methods to its native view.
|
|
991
|
+
function addReload(view: FastImageRef | null) {
|
|
992
|
+
if (view) view.reload = () => Commands.reload(view as any)
|
|
993
|
+
}
|
|
994
|
+
|
|
970
995
|
const FastImageMemo = memo(FastImageBase)
|
|
971
996
|
|
|
972
|
-
// What a ref to
|
|
973
|
-
//
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
//
|
|
977
|
-
// a ref.
|
|
997
|
+
// What a ref to FastImageBackground gets (its view), and, with reload(), one
|
|
998
|
+
// to FastImage (its native view, which has the same methods, e.g. measure).
|
|
999
|
+
// ComponentRef, as React Native's types declare View as a class, and its
|
|
1000
|
+
// Strict TypeScript API (opt-in from 0.80, the default from 0.87) as a
|
|
1001
|
+
// function component that takes a ref.
|
|
978
1002
|
type ViewRef = React.ComponentRef<typeof View>
|
|
979
1003
|
|
|
1004
|
+
/**
|
|
1005
|
+
* What a ref to `FastImage` gets: its native view, with a View's methods
|
|
1006
|
+
* (e.g. `measure`), and `reload()`. E.g. `useRef<FastImageRef>(null)`.
|
|
1007
|
+
*/
|
|
1008
|
+
export type FastImageRef = ViewRef & {
|
|
1009
|
+
/**
|
|
1010
|
+
* Loads the image again, e.g. to try again after `onError`. The image
|
|
1011
|
+
* showing stays until the new one has loaded, and the events are sent as
|
|
1012
|
+
* for a new source. An image in the cache loads from it.
|
|
1013
|
+
*/
|
|
1014
|
+
reload(): void
|
|
1015
|
+
}
|
|
1016
|
+
|
|
980
1017
|
const FastImageComponent: React.ForwardRefExoticComponent<
|
|
981
|
-
FastImageProps & React.RefAttributes<
|
|
982
|
-
> = forwardRef((props: FastImageProps, ref: React.Ref<
|
|
1018
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
1019
|
+
> = forwardRef((props: FastImageProps, ref: React.Ref<FastImageRef>) => (
|
|
983
1020
|
<FastImageMemo forwardedRef={ref} {...props} />
|
|
984
1021
|
))
|
|
985
1022
|
|
|
@@ -1112,7 +1149,7 @@ export interface FastImageDefaults {
|
|
|
1112
1149
|
}
|
|
1113
1150
|
|
|
1114
1151
|
const FastImage: React.ForwardRefExoticComponent<
|
|
1115
|
-
FastImageProps & React.RefAttributes<
|
|
1152
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
1116
1153
|
> &
|
|
1117
1154
|
FastImageStaticProperties = FastImageComponent as any
|
|
1118
1155
|
|
|
@@ -1223,7 +1260,7 @@ export interface FastImageBackgroundProps extends Omit<
|
|
|
1223
1260
|
/** The image's style. */
|
|
1224
1261
|
imageStyle?: StyleProp<ImageStyle>
|
|
1225
1262
|
/** A ref to the image (the FastImage inside). */
|
|
1226
|
-
imageRef?: React.Ref<
|
|
1263
|
+
imageRef?: React.Ref<FastImageRef>
|
|
1227
1264
|
/** Content shown on top of the image. */
|
|
1228
1265
|
children?: React.ReactNode
|
|
1229
1266
|
}
|
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}
|
|
@@ -383,10 +399,13 @@ function assetScale(asset: PackagerAsset): number {
|
|
|
383
399
|
)
|
|
384
400
|
}
|
|
385
401
|
|
|
402
|
+
// What sizedSources reads of a source.
|
|
403
|
+
type SizedSource = Pick<Source, 'uri' | 'width' | 'height' | 'scale'>
|
|
404
|
+
|
|
386
405
|
// One of several sizes: a require()d image in react-native-web's asset
|
|
387
406
|
// registry as a source with its file's size (Expo's web builds make it a
|
|
388
407
|
// { uri, width, height } object already).
|
|
389
|
-
function sizedSource(source:
|
|
408
|
+
function sizedSource(source: SizedSource | number): SizedSource | undefined {
|
|
390
409
|
if (typeof source !== 'number') return source
|
|
391
410
|
const asset: PackagerAsset | undefined = getAssetByID(source)
|
|
392
411
|
return asset
|
|
@@ -406,10 +425,12 @@ function sizedSource(source: Source | number): Source | undefined {
|
|
|
406
425
|
// is laid out at; it only applies to lazy images, and otherwise, or in
|
|
407
426
|
// browsers without it, the next value, 100vw (the viewport's width). src is the largest, for browsers without
|
|
408
427
|
// srcset.
|
|
409
|
-
function sizedSources(entries: (
|
|
428
|
+
function sizedSources(entries: (SizedSource | number)[]) {
|
|
410
429
|
const sources = entries.map(sizedSource)
|
|
411
430
|
const widths = sources
|
|
412
|
-
.filter(
|
|
431
|
+
.filter(
|
|
432
|
+
(source): source is SizedSource => !!source?.uri && !!source.width,
|
|
433
|
+
)
|
|
413
434
|
.map((source) => ({
|
|
414
435
|
uri: source.uri as string,
|
|
415
436
|
width: Math.round((source.width ?? 0) * (source.scale ?? 1)),
|
|
@@ -653,11 +674,15 @@ const tintStyle = {
|
|
|
653
674
|
|
|
654
675
|
const FastImageMemo = memo(FastImageBase)
|
|
655
676
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
677
|
+
// What a ref to FastImageBackground gets, as in index.tsx: a View's
|
|
678
|
+
// (react-native-web's element). FastImage's has reload() too (FastImageRef).
|
|
679
|
+
type ViewRef = React.ComponentRef<typeof View>
|
|
680
|
+
|
|
681
|
+
const FastImageComponent: React.ForwardRefExoticComponent<
|
|
682
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
683
|
+
> = forwardRef((props: FastImageProps, ref: React.Ref<FastImageRef>) => (
|
|
684
|
+
<FastImageMemo forwardedRef={ref} {...props} />
|
|
685
|
+
))
|
|
661
686
|
|
|
662
687
|
FastImageComponent.displayName = 'FastImage'
|
|
663
688
|
|
|
@@ -672,7 +697,9 @@ function getSize(uri: string) {
|
|
|
672
697
|
})
|
|
673
698
|
}
|
|
674
699
|
|
|
675
|
-
const FastImage: React.
|
|
700
|
+
const FastImage: React.ForwardRefExoticComponent<
|
|
701
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
702
|
+
> &
|
|
676
703
|
FastImageStaticProperties = FastImageComponent as any
|
|
677
704
|
|
|
678
705
|
FastImage.cacheControl = cacheControl
|
|
@@ -720,33 +747,29 @@ const styles = StyleSheet.create({
|
|
|
720
747
|
},
|
|
721
748
|
})
|
|
722
749
|
|
|
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
|
-
)
|
|
750
|
+
export const FastImageBackground: React.ForwardRefExoticComponent<
|
|
751
|
+
FastImageBackgroundProps & React.RefAttributes<ViewRef>
|
|
752
|
+
> = forwardRef(
|
|
753
|
+
(
|
|
754
|
+
{
|
|
755
|
+
style,
|
|
756
|
+
imageStyle,
|
|
757
|
+
imageRef,
|
|
758
|
+
children,
|
|
759
|
+
...props
|
|
760
|
+
}: FastImageBackgroundProps,
|
|
761
|
+
ref: React.Ref<ViewRef>,
|
|
762
|
+
) => (
|
|
763
|
+
<View style={style} ref={ref}>
|
|
764
|
+
<FastImageComponent
|
|
765
|
+
{...props}
|
|
766
|
+
style={[StyleSheet.absoluteFill, imageStyle]}
|
|
767
|
+
ref={imageRef}
|
|
768
|
+
/>
|
|
769
|
+
{children}
|
|
770
|
+
</View>
|
|
771
|
+
),
|
|
772
|
+
)
|
|
750
773
|
|
|
751
774
|
FastImageBackground.displayName = 'FastImageBackground'
|
|
752
775
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// React Native's `ImageURISource` cache values, which the views' `source`
|
|
2
|
+
// takes so a source typed for React Native's Image works as it is. Not
|
|
3
|
+
// documented: Fast Image's own values are the ones to use. Native reads
|
|
4
|
+
// 'only-if-cached' as 'cacheOnly', and the others as the default, 'immutable'.
|
|
5
|
+
import { warnOnce } from './warnings'
|
|
6
|
+
|
|
7
|
+
declare const __DEV__: boolean | undefined
|
|
8
|
+
|
|
9
|
+
export type ReactNativeCache =
|
|
10
|
+
| 'default'
|
|
11
|
+
| 'reload'
|
|
12
|
+
| 'force-cache'
|
|
13
|
+
| 'only-if-cached'
|
|
14
|
+
|
|
15
|
+
// Warns for a source with 'reload', as the image isn't loaded again each time.
|
|
16
|
+
export function warnIfReload(source: unknown) {
|
|
17
|
+
if (
|
|
18
|
+
typeof __DEV__ !== 'undefined' &&
|
|
19
|
+
__DEV__ &&
|
|
20
|
+
(source as { cache?: unknown } | null | undefined)?.cache === 'reload'
|
|
21
|
+
) {
|
|
22
|
+
warnOnce(
|
|
23
|
+
'cache-reload',
|
|
24
|
+
"react-native-fast-image: `cache: 'reload'` loads the image as the default, `'immutable'`: it isn't loaded again each time. For an image that changes at the same url, use `cache: 'web'` (it follows the server's cache headers), or change its `cacheKey`.",
|
|
25
|
+
'warn',
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -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>
|