react-native-fast-image 10.3.0 → 10.5.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/src/index.web.tsx CHANGED
@@ -32,7 +32,11 @@ import { getAssetByID } from 'react-native-web/dist/modules/AssetRegistry'
32
32
  import { cacheControl, priority, resizeMode } from './constants'
33
33
  import { resolveObjectFit } from './objectFit'
34
34
  import { cssPosition, objectPositionOrCentered } from './objectPosition'
35
- import { dataUri, hasSourcePlaceholder } from './placeholders'
35
+ import {
36
+ dataUri,
37
+ hasSourcePlaceholder,
38
+ placeholderSource,
39
+ } from './placeholders'
36
40
  import { warnIfChildren, warnOnce } from './warnings'
37
41
  import type {
38
42
  CachePathResult,
@@ -82,6 +86,7 @@ function FastImageBase({
82
86
  objectPosition,
83
87
  loading,
84
88
  sizes,
89
+ dynamicRangeLimit,
85
90
  forwardedRef,
86
91
  // On the wrapper, the view the layout and touches are for here (on
87
92
  // native, FastImage's only view).
@@ -131,11 +136,20 @@ function FastImageBase({
131
136
  const blurUri =
132
137
  resolveUri(dataUri(shared(source, 'blurDataUri', preview))) ??
133
138
  thumbhashUri(shared(source, 'thumbhash', preview))
139
+ // `placeholder` as a url (or a source with one) shows over the other
140
+ // placeholders as it loads, as on iOS and Android. The browser loads it
141
+ // like any image: it can't look in its cache only (cache: 'cacheOnly').
142
+ const urlPlaceholder = placeholderSource(placeholder)?.uri
143
+ const [failedPlaceholder, setFailedPlaceholder] = useState<string>()
134
144
  // A thumbhash or blurDataUri in `placeholder` isn't an image to show as it
135
- // is: defaultSource still is.
145
+ // is, nor is a url (above): defaultSource still is.
136
146
  const defaultUri =
137
147
  blurUri ??
138
- resolveUri(preview ? defaultSource : (placeholder ?? defaultSource))
148
+ resolveUri(
149
+ preview || urlPlaceholder
150
+ ? defaultSource
151
+ : (placeholder ?? defaultSource),
152
+ )
139
153
  // Whether the image for `key` loaded, or failed.
140
154
  const [result, setResult] = useState<{ key: string; ok: boolean }>()
141
155
  const ok = result && result.key === key ? result.ok : undefined
@@ -155,7 +169,7 @@ function FastImageBase({
155
169
  .filter(Boolean)
156
170
  .join(' ') || undefined
157
171
  // The placeholder shows until the image has loaded, and if it fails.
158
- const showDefault = !!defaultUri && ok !== true
172
+ const showDefault = ok !== true
159
173
 
160
174
  useClientLayoutEffect(() => {
161
175
  if (key) onLoadStart?.()
@@ -179,6 +193,7 @@ function FastImageBase({
179
193
  // Loaded when the image is (a data uri needs no request).
180
194
  loading={loading}
181
195
  priority={priority}
196
+ dynamicRangeLimit={dynamicRangeLimit}
182
197
  // A source's placeholder stands for an image whose size
183
198
  // isn't known yet: as contain for scale-down and none,
184
199
  // which show an image at its own size.
@@ -205,6 +220,28 @@ function FastImageBase({
205
220
  alt={key ? '' : (label ?? '')}
206
221
  />
207
222
  ) : null}
223
+ {urlPlaceholder && showDefault ? (
224
+ <Picture
225
+ key={`placeholder ${urlPlaceholder}`}
226
+ src={urlPlaceholder}
227
+ loading={loading}
228
+ // It only helps if it comes before the image, as on iOS
229
+ // and Android.
230
+ priority="high"
231
+ dynamicRangeLimit={dynamicRangeLimit}
232
+ // It stands for the image, as a source's preview does.
233
+ fit={
234
+ fit === 'scale-down' || fit === 'none' ? 'contain' : fit
235
+ }
236
+ position={position}
237
+ // Tinted, not blurred, as on iOS and Android.
238
+ filter={tintColor ? `url(#${tintId})` : undefined}
239
+ // Nothing (no broken image icon) if it fails.
240
+ hidden={failedPlaceholder === urlPlaceholder}
241
+ onError={() => setFailedPlaceholder(urlPlaceholder)}
242
+ alt=""
243
+ />
244
+ ) : null}
208
245
  {key && src ? (
209
246
  <Picture
210
247
  key={several ? 'sizes' : key}
@@ -213,6 +250,7 @@ function FastImageBase({
213
250
  sizes={sizes}
214
251
  loading={loading}
215
252
  priority={priority}
253
+ dynamicRangeLimit={dynamicRangeLimit}
216
254
  fit={fit}
217
255
  position={position}
218
256
  filter={filter}
@@ -414,6 +452,7 @@ function Picture({
414
452
  sizes,
415
453
  loading,
416
454
  priority,
455
+ dynamicRangeLimit,
417
456
  fit,
418
457
  position,
419
458
  filter,
@@ -429,6 +468,9 @@ function Picture({
429
468
  loading?: 'lazy' | 'eager'
430
469
  // source.priority, as the <img>'s fetchpriority.
431
470
  priority?: Priority
471
+ // The prop, as CSS's dynamic-range-limit (the browser's default when
472
+ // not set).
473
+ dynamicRangeLimit?: 'standard' | 'constrained' | 'no-limit'
432
474
  fit: ObjectFit
433
475
  // objectPosition, as CSS.
434
476
  position: string
@@ -513,6 +555,8 @@ function Picture({
513
555
  objectPosition: position,
514
556
  filter,
515
557
  opacity: hidden ? 0 : undefined,
558
+ // Not in React's CSS types yet.
559
+ ...({ dynamicRangeLimit } as object),
516
560
  }}
517
561
  onLoad={(event) =>
518
562
  loaded(event.currentTarget as unknown as LoadedImage)
@@ -1,20 +1,37 @@
1
- // Placeholders given as strings (`placeholder`, and a source's `blurDataUri`)
2
- // are data uris, shared by the native (index.tsx) and web (index.web.tsx)
3
- // versions, or for `placeholder`, the name of an image bundled with the app.
4
- // They're decoded at once, so they show in the view's first frame; a url
5
- // would load like an image (and on iOS and Android, not at all), so it's
6
- // ignored, with a warning in development.
7
- import type { SourcePlaceholder } from './index'
1
+ // Placeholders given as strings, shared by the native (index.tsx) and web
2
+ // (index.web.tsx) versions: a source's `blurDataUri` is a data uri, decoded at
3
+ // once, so it shows in the view's first frame (a url there is ignored, with a
4
+ // warning in development). `placeholder` can also be the name of an image
5
+ // bundled with the app, or a url, loaded as a source (placeholderSource).
6
+ import type { PlaceholderSource, SourcePlaceholder } from './index'
8
7
  import { warnOnce } from './warnings'
9
8
 
10
9
  // Metro's (and React Native's Jest preset's) development flag; other web
11
10
  // bundlers may not define it.
12
11
  declare const __DEV__: boolean | undefined
13
12
 
13
+ const SCHEME = /^[a-z][a-z\d+.-]*:/i
14
+
14
15
  // Whether `placeholder` is the name of an image bundled with the app (an iOS
15
16
  // asset catalog image, an Android drawable): no scheme, not a path.
16
17
  export function isImageName(value: string): boolean {
17
- return !/^[a-z][a-z\d+.-]*:/i.test(value) && !value.startsWith('/')
18
+ return !SCHEME.test(value) && !value.startsWith('/')
19
+ }
20
+
21
+ // `placeholder` as a source to load: an http or https url, as a string or a
22
+ // source object's uri. Undefined for anything else (a local file or a data uri
23
+ // is decoded at once, as the native placeholder prop).
24
+ export function placeholderSource(
25
+ value: unknown,
26
+ ): PlaceholderSource | undefined {
27
+ const uri =
28
+ typeof value === 'string'
29
+ ? value
30
+ : value && typeof value === 'object'
31
+ ? (value as PlaceholderSource).uri
32
+ : undefined
33
+ if (typeof uri !== 'string' || !/^https?:/i.test(uri)) return undefined
34
+ return typeof value === 'string' ? { uri } : (value as PlaceholderSource)
18
35
  }
19
36
 
20
37
  // Whether a source has a thumbhash or blurDataUri, or `placeholder` is one
@@ -37,7 +54,7 @@ export function dataUri(value: unknown): string | undefined {
37
54
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
38
55
  warnOnce(
39
56
  'placeholder-url',
40
- `react-native-fast-image: \`placeholder\` and \`blurDataUri\` don't load urls: "${value.slice(0, 60)}" is ignored. Use a data uri (data:image/…), or for \`placeholder\`, a require()d image or the name of an image bundled with the app.`,
57
+ `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\`.`,
41
58
  'warn',
42
59
  )
43
60
  }
@@ -45,8 +45,10 @@ export interface NativeProps extends ViewProps {
45
45
  source?: CodegenTypes.UnsafeMixed
46
46
  // Several sources (sizes of one image), picked from for the view's size.
47
47
  sources?: CodegenTypes.UnsafeMixed
48
- // The `placeholder` prop: a resolved asset source ({ uri, width, height,
49
- // scale }) or a data uri ({ uri } on iOS; the uri on Android).
48
+ // The `placeholder` prop: a url to load apart from the image, as a source
49
+ // ({ uri, headers, cacheKey, cache }), or an image shown at once: a
50
+ // resolved asset source ({ uri, width, height, scale }) or a data uri
51
+ // ({ uri } on iOS; the uri on Android, as a string).
50
52
  placeholder?: CodegenTypes.UnsafeMixed
51
53
  // What shows in place of placeholder while a source loads: its ThumbHash,
52
54
  // or its blurDataUri (blurred natively), by the source's index ('' for
@@ -70,6 +72,9 @@ export interface NativeProps extends ViewProps {
70
72
  transitionBetweenImages?: boolean
71
73
  transitionSkipOnCacheHit?: string
72
74
  downsample?: CodegenTypes.WithDefault<boolean, true>
75
+ // standard, constrained or no-limit ('' when not set). iOS and tvOS 17+
76
+ // (Android ignores it).
77
+ dynamicRangeLimit?: string
73
78
  blurRadius?: CodegenTypes.Float
74
79
  // The events JS has a handler for, as bits: 1 onFastImageLoadStart,
75
80
  // 2 onFastImageProgress, 4 onFastImageLoad, 8 onFastImageError, 16