react-native-fast-image 10.5.0 → 10.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -4
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +19 -14
- package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +97 -29
- package/dist/composeRefs.d.ts +2 -0
- package/dist/index.cjs.js +33 -25
- package/dist/index.cjs.js.flow +3 -1
- package/dist/index.cjs.web.js +52 -13
- package/dist/index.cjs.web.js.flow +3 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.js +34 -26
- package/dist/index.js.flow +3 -1
- package/dist/index.web.d.ts +5 -3
- package/dist/index.web.js +53 -14
- package/dist/index.web.js.flow +3 -1
- package/dist/specs/FastImageViewNativeComponent.d.ts +5 -2
- package/ios/FastImage/FFFThumbHash.h +3 -0
- package/ios/FastImage/FFFThumbHash.m +5 -1
- package/ios/FastImage/FFFastImageView.h +13 -7
- package/ios/FastImage/FFFastImageView.m +31 -19
- package/ios/FastImage/FFFastImageViewComponentView.mm +72 -14
- package/package.json +1 -1
- package/src/composeRefs.ts +29 -0
- package/src/index.js.flow +3 -1
- package/src/index.tsx +64 -81
- package/src/index.web.tsx +99 -53
- package/src/specs/FastImageViewNativeComponent.ts +25 -16
package/src/index.js.flow
CHANGED
|
@@ -145,7 +145,7 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
145
145
|
onLoadStart?: ?() => void,
|
|
146
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
147
147
|
|
|
148
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
149
|
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
150
150
|
// Deprecated: use placeholder, which is the same.
|
|
151
151
|
defaultSource?: ?number,
|
|
@@ -196,4 +196,6 @@ declare export default class FastImage extends React$Component<FastImageProps> {
|
|
|
196
196
|
source: FastImageSource,
|
|
197
197
|
file: string,
|
|
198
198
|
) => Promise<CachePathResult>;
|
|
199
|
+
// On a ref: loads the image again, e.g. to try again after onError.
|
|
200
|
+
reload(): void;
|
|
199
201
|
}
|
package/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). */
|
|
@@ -362,9 +363,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
362
363
|
* - `FastImage.preload`, `getCachePath` and `writeToCache` take one size:
|
|
363
364
|
* pass the one a view will show. An array fails with
|
|
364
365
|
* `{ ok: false, error }`.
|
|
366
|
+
* - A `require()`d image can be one of the sizes: its size is the file's.
|
|
365
367
|
* - An array of one is the same as that source.
|
|
366
368
|
*/
|
|
367
|
-
source?: Source | Source
|
|
369
|
+
source?: Source | Array<Source | ImageRequireSource> | ImageRequireSource
|
|
368
370
|
/**
|
|
369
371
|
* A placeholder shown while the image loads, and if it fails to load:
|
|
370
372
|
*
|
|
@@ -398,7 +400,8 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
398
400
|
| SourcePlaceholder
|
|
399
401
|
| PlaceholderSource
|
|
400
402
|
/**
|
|
401
|
-
* @deprecated Use `placeholder`, which does the same.
|
|
403
|
+
* @deprecated Use `placeholder`, which does the same. With both,
|
|
404
|
+
* `placeholder` shows.
|
|
402
405
|
*/
|
|
403
406
|
defaultSource?: ImageRequireSource
|
|
404
407
|
/**
|
|
@@ -707,25 +710,6 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
707
710
|
children?: "FastImage doesn't take children: use FastImageBackground for content over an image"
|
|
708
711
|
}
|
|
709
712
|
|
|
710
|
-
// A source without its thumbhash and blurDataUri.
|
|
711
|
-
function withoutPlaceholders<T>(source: T): T {
|
|
712
|
-
if (!hasSourcePlaceholder(source)) return source
|
|
713
|
-
const {
|
|
714
|
-
thumbhash: _thumbhash,
|
|
715
|
-
blurDataUri: _blurDataUri,
|
|
716
|
-
...rest
|
|
717
|
-
} = source as Source
|
|
718
|
-
return rest as T
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// What the native view shows while a source loads: its ThumbHash (decoded
|
|
722
|
-
// natively), or its blurDataUri, or '' for neither.
|
|
723
|
-
function sourcePlaceholder(source: unknown): string {
|
|
724
|
-
if (!source || typeof source !== 'object') return ''
|
|
725
|
-
const { thumbhash, blurDataUri } = source as Source
|
|
726
|
-
return thumbhash || dataUri(blurDataUri) || ''
|
|
727
|
-
}
|
|
728
|
-
|
|
729
713
|
const resolvePlaceholder = (
|
|
730
714
|
// A source object here has no url (e.g. { uri: name }, a bundled image).
|
|
731
715
|
placeholder?: ImageRequireSource | string | PlaceholderSource,
|
|
@@ -911,58 +895,41 @@ function FastImageBase({
|
|
|
911
895
|
const fit = resolveObjectFit(objectFit, style)
|
|
912
896
|
const position = objectPositionOrCentered(objectPosition)
|
|
913
897
|
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
// Several sources are picked from natively, for the view's size; one
|
|
924
|
-
// in an array is a plain source.
|
|
925
|
-
const sources = Array.isArray(source)
|
|
926
|
-
? source.length === 1
|
|
927
|
-
? undefined
|
|
928
|
-
: source
|
|
929
|
-
: undefined
|
|
930
|
-
const single = Array.isArray(source) ? source[0] : source
|
|
931
|
-
// Sources' thumbhashes and blurDataUris go to the native view apart
|
|
932
|
-
// from the sources, so a new one for the same image doesn't load it
|
|
933
|
-
// again, by the source's index ('' for none). A source's own wins
|
|
934
|
-
// over `placeholder`'s.
|
|
935
|
-
const placeholders = (sources ?? [single]).map(
|
|
936
|
-
(entry) => sourcePlaceholder(entry) || shared,
|
|
937
|
-
)
|
|
938
|
-
return {
|
|
939
|
-
resolvedSource: sources
|
|
940
|
-
? undefined
|
|
941
|
-
: Image.resolveAssetSource(withoutPlaceholders(single) as any),
|
|
942
|
-
sources: sources?.some(hasSourcePlaceholder)
|
|
943
|
-
? sources.map(withoutPlaceholders)
|
|
944
|
-
: sources,
|
|
945
|
-
sourcePlaceholders: placeholders.some(Boolean)
|
|
946
|
-
? placeholders
|
|
947
|
-
: undefined,
|
|
898
|
+
// One source, or several in an array, picked from natively for the
|
|
899
|
+
// view's size (one in an array is a plain source), with their thumbhashes
|
|
900
|
+
// and blurDataUris: the native view doesn't load the image again when
|
|
901
|
+
// only those change. Memoized on source, so an unchanged source reaches
|
|
902
|
+
// the native view as the same object, and React's prop diff stops at it.
|
|
903
|
+
const resolvedSource = useMemo(() => {
|
|
904
|
+
// A blurDataUri that isn't a data uri is ignored, with a warning.
|
|
905
|
+
for (const entry of [source].flat()) {
|
|
906
|
+
dataUri((entry as Source | undefined)?.blurDataUri)
|
|
948
907
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
)
|
|
908
|
+
const single =
|
|
909
|
+
Array.isArray(source) && source.length === 1 ? source[0] : source
|
|
910
|
+
return Array.isArray(single)
|
|
911
|
+
? single.map((entry) => Image.resolveAssetSource(entry as any))
|
|
912
|
+
: Image.resolveAssetSource(single as any)
|
|
913
|
+
}, [source])
|
|
914
|
+
// `placeholder` as the native view takes it: a thumbhash or blurDataUri
|
|
915
|
+
// as it is (for the sources without their own), a url as a source loaded
|
|
916
|
+
// apart from the image, or an image shown at once. defaultSource only
|
|
917
|
+
// applies without one (an empty thumbhash or blurDataUri is none).
|
|
918
|
+
const resolvedPlaceholder = hasSourcePlaceholder(placeholder)
|
|
919
|
+
? placeholder.thumbhash || dataUri(placeholder.blurDataUri)
|
|
920
|
+
? placeholder
|
|
921
|
+
: resolvePlaceholder(defaultSource)
|
|
922
|
+
: (placeholderSource(placeholder) ??
|
|
923
|
+
resolvePlaceholder(placeholder ?? defaultSource))
|
|
924
|
+
const ref = useMemo(
|
|
925
|
+
() => composeRefs<FastImageRef>(addReload, forwardedRef),
|
|
926
|
+
[forwardedRef],
|
|
927
|
+
)
|
|
961
928
|
|
|
962
929
|
return (
|
|
963
930
|
<FastImageView
|
|
964
931
|
{...props}
|
|
965
|
-
ref={
|
|
932
|
+
ref={ref}
|
|
966
933
|
// Clips the image to borderRadius by default, as React Native's
|
|
967
934
|
// Image does.
|
|
968
935
|
style={[styles.image, style]}
|
|
@@ -974,8 +941,6 @@ function FastImageBase({
|
|
|
974
941
|
defaultTransition ?? builtInTransition(),
|
|
975
942
|
)}
|
|
976
943
|
source={resolvedSource}
|
|
977
|
-
sources={sources}
|
|
978
|
-
sourcePlaceholders={sourcePlaceholders}
|
|
979
944
|
placeholder={resolvedPlaceholder}
|
|
980
945
|
onFastImageLoadStart={onLoadStart}
|
|
981
946
|
onFastImageProgress={onProgress}
|
|
@@ -1007,19 +972,37 @@ function FastImageBase({
|
|
|
1007
972
|
)
|
|
1008
973
|
}
|
|
1009
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
|
+
|
|
1010
981
|
const FastImageMemo = memo(FastImageBase)
|
|
1011
982
|
|
|
1012
|
-
// What a ref to
|
|
1013
|
-
//
|
|
1014
|
-
//
|
|
1015
|
-
//
|
|
1016
|
-
//
|
|
1017
|
-
// 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.
|
|
1018
988
|
type ViewRef = React.ComponentRef<typeof View>
|
|
1019
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
|
+
|
|
1020
1003
|
const FastImageComponent: React.ForwardRefExoticComponent<
|
|
1021
|
-
FastImageProps & React.RefAttributes<
|
|
1022
|
-
> = forwardRef((props: FastImageProps, ref: React.Ref<
|
|
1004
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
1005
|
+
> = forwardRef((props: FastImageProps, ref: React.Ref<FastImageRef>) => (
|
|
1023
1006
|
<FastImageMemo forwardedRef={ref} {...props} />
|
|
1024
1007
|
))
|
|
1025
1008
|
|
|
@@ -1152,7 +1135,7 @@ export interface FastImageDefaults {
|
|
|
1152
1135
|
}
|
|
1153
1136
|
|
|
1154
1137
|
const FastImage: React.ForwardRefExoticComponent<
|
|
1155
|
-
FastImageProps & React.RefAttributes<
|
|
1138
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
1156
1139
|
> &
|
|
1157
1140
|
FastImageStaticProperties = FastImageComponent as any
|
|
1158
1141
|
|
|
@@ -1263,7 +1246,7 @@ export interface FastImageBackgroundProps extends Omit<
|
|
|
1263
1246
|
/** The image's style. */
|
|
1264
1247
|
imageStyle?: StyleProp<ImageStyle>
|
|
1265
1248
|
/** A ref to the image (the FastImage inside). */
|
|
1266
|
-
imageRef?: React.Ref<
|
|
1249
|
+
imageRef?: React.Ref<FastImageRef>
|
|
1267
1250
|
/** Content shown on top of the image. */
|
|
1268
1251
|
children?: React.ReactNode
|
|
1269
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,15 +154,21 @@ 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
|
-
//
|
|
145
|
-
//
|
|
160
|
+
// defaultSource only shows without `placeholder`, as on iOS and Android,
|
|
161
|
+
// or in place of a thumbhash or blurDataUri the web can't show (one of
|
|
162
|
+
// several sizes', or a thumbhash without react-native-fast-image/thumbhash,
|
|
163
|
+
// or an empty one). Not with a url (above).
|
|
146
164
|
const defaultUri =
|
|
147
165
|
blurUri ??
|
|
148
166
|
resolveUri(
|
|
149
|
-
preview
|
|
167
|
+
preview
|
|
150
168
|
? defaultSource
|
|
151
|
-
:
|
|
169
|
+
: urlPlaceholder
|
|
170
|
+
? undefined
|
|
171
|
+
: (placeholder ?? defaultSource),
|
|
152
172
|
)
|
|
153
173
|
// Whether the image for `key` loaded, or failed.
|
|
154
174
|
const [result, setResult] = useState<{ key: string; ok: boolean }>()
|
|
@@ -175,7 +195,7 @@ function FastImageBase({
|
|
|
175
195
|
if (key) onLoadStart?.()
|
|
176
196
|
// Once per image the browser is asked for.
|
|
177
197
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
178
|
-
}, [key])
|
|
198
|
+
}, [key, reloads])
|
|
179
199
|
|
|
180
200
|
return (
|
|
181
201
|
<View
|
|
@@ -184,7 +204,7 @@ function FastImageBase({
|
|
|
184
204
|
style={[styles.container, style]}
|
|
185
205
|
onLayout={onLayout}
|
|
186
206
|
pointerEvents={pointerEvents}
|
|
187
|
-
ref={
|
|
207
|
+
ref={ref}
|
|
188
208
|
>
|
|
189
209
|
{defaultUri && showDefault ? (
|
|
190
210
|
<Picture
|
|
@@ -222,7 +242,7 @@ function FastImageBase({
|
|
|
222
242
|
) : null}
|
|
223
243
|
{urlPlaceholder && showDefault ? (
|
|
224
244
|
<Picture
|
|
225
|
-
key={`placeholder ${
|
|
245
|
+
key={`placeholder ${placeholderKey}`}
|
|
226
246
|
src={urlPlaceholder}
|
|
227
247
|
loading={loading}
|
|
228
248
|
// It only helps if it comes before the image, as on iOS
|
|
@@ -237,14 +257,14 @@ function FastImageBase({
|
|
|
237
257
|
// Tinted, not blurred, as on iOS and Android.
|
|
238
258
|
filter={tintColor ? `url(#${tintId})` : undefined}
|
|
239
259
|
// Nothing (no broken image icon) if it fails.
|
|
240
|
-
hidden={failedPlaceholder ===
|
|
241
|
-
onError={() => setFailedPlaceholder(
|
|
260
|
+
hidden={failedPlaceholder === placeholderKey}
|
|
261
|
+
onError={() => setFailedPlaceholder(placeholderKey)}
|
|
242
262
|
alt=""
|
|
243
263
|
/>
|
|
244
264
|
) : null}
|
|
245
265
|
{key && src ? (
|
|
246
266
|
<Picture
|
|
247
|
-
key={several ? 'sizes' : key}
|
|
267
|
+
key={`${several ? 'sizes' : key} ${reloads}`}
|
|
248
268
|
src={src}
|
|
249
269
|
srcSet={srcSet}
|
|
250
270
|
sizes={sizes}
|
|
@@ -341,14 +361,7 @@ function resolveUri(source: unknown): string | undefined {
|
|
|
341
361
|
if (typeof source === 'number') {
|
|
342
362
|
const asset: PackagerAsset | undefined = getAssetByID(source)
|
|
343
363
|
if (!asset) return undefined
|
|
344
|
-
const scale = asset
|
|
345
|
-
(best, scale) =>
|
|
346
|
-
Math.abs(scale - PixelRatio.get()) <
|
|
347
|
-
Math.abs(best - PixelRatio.get())
|
|
348
|
-
? scale
|
|
349
|
-
: best,
|
|
350
|
-
asset.scales[0] ?? 1,
|
|
351
|
-
)
|
|
364
|
+
const scale = assetScale(asset)
|
|
352
365
|
const suffix = scale !== 1 ? `@${scale}x` : ''
|
|
353
366
|
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`
|
|
354
367
|
} else if (typeof source === 'string') {
|
|
@@ -370,6 +383,36 @@ type PackagerAsset = {
|
|
|
370
383
|
name: string
|
|
371
384
|
type: string
|
|
372
385
|
scales: number[]
|
|
386
|
+
width?: number
|
|
387
|
+
height?: number
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// The scale of an asset's file closest to the screen's.
|
|
391
|
+
function assetScale(asset: PackagerAsset): number {
|
|
392
|
+
return asset.scales.reduce(
|
|
393
|
+
(best, scale) =>
|
|
394
|
+
Math.abs(scale - PixelRatio.get()) <
|
|
395
|
+
Math.abs(best - PixelRatio.get())
|
|
396
|
+
? scale
|
|
397
|
+
: best,
|
|
398
|
+
asset.scales[0] ?? 1,
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// One of several sizes: a require()d image in react-native-web's asset
|
|
403
|
+
// registry as a source with its file's size (Expo's web builds make it a
|
|
404
|
+
// { uri, width, height } object already).
|
|
405
|
+
function sizedSource(source: Source | number): Source | undefined {
|
|
406
|
+
if (typeof source !== 'number') return source
|
|
407
|
+
const asset: PackagerAsset | undefined = getAssetByID(source)
|
|
408
|
+
return asset
|
|
409
|
+
? {
|
|
410
|
+
uri: resolveUri(source),
|
|
411
|
+
width: asset.width,
|
|
412
|
+
height: asset.height,
|
|
413
|
+
scale: assetScale(asset),
|
|
414
|
+
}
|
|
415
|
+
: undefined
|
|
373
416
|
}
|
|
374
417
|
|
|
375
418
|
// Several sizes of an image, as a srcset (each size's width, times its
|
|
@@ -379,9 +422,10 @@ type PackagerAsset = {
|
|
|
379
422
|
// is laid out at; it only applies to lazy images, and otherwise, or in
|
|
380
423
|
// browsers without it, the next value, 100vw (the viewport's width). src is the largest, for browsers without
|
|
381
424
|
// srcset.
|
|
382
|
-
function sizedSources(
|
|
425
|
+
function sizedSources(entries: (Source | number)[]) {
|
|
426
|
+
const sources = entries.map(sizedSource)
|
|
383
427
|
const widths = sources
|
|
384
|
-
.filter((source) => source?.uri && source.width)
|
|
428
|
+
.filter((source): source is Source => !!source?.uri && !!source.width)
|
|
385
429
|
.map((source) => ({
|
|
386
430
|
uri: source.uri as string,
|
|
387
431
|
width: Math.round((source.width ?? 0) * (source.scale ?? 1)),
|
|
@@ -625,11 +669,15 @@ const tintStyle = {
|
|
|
625
669
|
|
|
626
670
|
const FastImageMemo = memo(FastImageBase)
|
|
627
671
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
+
))
|
|
633
681
|
|
|
634
682
|
FastImageComponent.displayName = 'FastImage'
|
|
635
683
|
|
|
@@ -644,7 +692,9 @@ function getSize(uri: string) {
|
|
|
644
692
|
})
|
|
645
693
|
}
|
|
646
694
|
|
|
647
|
-
const FastImage: React.
|
|
695
|
+
const FastImage: React.ForwardRefExoticComponent<
|
|
696
|
+
FastImageProps & React.RefAttributes<FastImageRef>
|
|
697
|
+
> &
|
|
648
698
|
FastImageStaticProperties = FastImageComponent as any
|
|
649
699
|
|
|
650
700
|
FastImage.cacheControl = cacheControl
|
|
@@ -692,33 +742,29 @@ const styles = StyleSheet.create({
|
|
|
692
742
|
},
|
|
693
743
|
})
|
|
694
744
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
{children}
|
|
719
|
-
</View>
|
|
720
|
-
),
|
|
721
|
-
)
|
|
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
|
+
)
|
|
722
768
|
|
|
723
769
|
FastImageBackground.displayName = 'FastImageBackground'
|
|
724
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
|
|
|
@@ -40,21 +41,18 @@ type LoadEndEvent = Readonly<{
|
|
|
40
41
|
}>
|
|
41
42
|
|
|
42
43
|
export interface NativeProps extends ViewProps {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
44
|
+
// The `source` prop: a source ({ uri, headers, priority, cache, ... },
|
|
45
|
+
// with its thumbhash or blurDataUri), or several (sizes of one image) in
|
|
46
|
+
// an array, picked from for the view's size. A change to the thumbhashes
|
|
47
|
+
// and blurDataUris only doesn't load the image again.
|
|
45
48
|
source?: CodegenTypes.UnsafeMixed
|
|
46
|
-
//
|
|
47
|
-
sources
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
49
|
+
// The `placeholder` prop: a thumbhash or blurDataUri ({ thumbhash } or
|
|
50
|
+
// { blurDataUri }, for the sources without their own), a url to load
|
|
51
|
+
// apart from the image, as a source ({ uri, headers, cacheKey, cache }),
|
|
52
|
+
// or an image shown at once: a resolved asset source ({ uri, width,
|
|
53
|
+
// height, scale }) or a data uri ({ uri } on iOS; the uri on Android, as
|
|
54
|
+
// a string).
|
|
52
55
|
placeholder?: CodegenTypes.UnsafeMixed
|
|
53
|
-
// What shows in place of placeholder while a source loads: its ThumbHash,
|
|
54
|
-
// or its blurDataUri (blurred natively), by the source's index ('' for
|
|
55
|
-
// none; one for a single source). Apart from the sources, so a new one
|
|
56
|
-
// for the same image doesn't load it again.
|
|
57
|
-
sourcePlaceholders?: ReadonlyArray<string>
|
|
58
56
|
// objectFit, resolved (fill, contain, cover, none or scale-down).
|
|
59
57
|
objectFit?: CodegenTypes.WithDefault<string, 'cover'>
|
|
60
58
|
// objectPosition, as CSS computes it: on each axis the image's left or top
|
|
@@ -89,6 +87,17 @@ export interface NativeProps extends ViewProps {
|
|
|
89
87
|
onFastImageLoadEnd?: CodegenTypes.DirectEventHandler<LoadEndEvent>
|
|
90
88
|
}
|
|
91
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
|
+
|
|
92
101
|
export default codegenNativeComponent<NativeProps>(
|
|
93
102
|
'FastImageView',
|
|
94
103
|
) as HostComponent<NativeProps>
|