react-native-fast-image 10.2.0 → 10.3.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 CHANGED
@@ -71,7 +71,7 @@ npx expo install react-native-fast-image
71
71
 
72
72
  It has native code, so it needs a [development build](https://docs.expo.dev/develop/development-builds/introduction/) (e.g. `npx expo run:ios`), not Expo Go.
73
73
 
74
- Needs React Native 0.83 or later with the New Architecture (Expo SDK 55 or later), iOS/tvOS 15.1 or later, and Android 7.0 (API 24) or later; on tvOS, it works with [React Native tvOS](https://github.com/react-native-tvos/react-native-tvos). Apps on the legacy architecture or older React Native can use FastImage 9 (`npm install react-native-fast-image@9`). Upgrading from FastImage 9: see [Upgrading from 9.x](docs/upgrading.md).
74
+ Needs React Native 0.83 or later with the New Architecture (Expo SDK 55 or later) and React 19, iOS/tvOS 15.1 or later, and Android 7.0 (API 24) or later; on tvOS, it works with [React Native tvOS](https://github.com/react-native-tvos/react-native-tvos). Apps on the legacy architecture or older React Native can use FastImage 9 (`npm install react-native-fast-image@9`). Upgrading from FastImage 9: see [Upgrading from 9.x](docs/upgrading.md).
75
75
 
76
76
  To set the cache's size when the app starts, see [Native config](#native-config).
77
77
 
@@ -161,6 +161,8 @@ A hint for which images to start loading first when several are waiting: `'high'
161
161
  - `'normal'`: the default.
162
162
  - `'high'`: e.g. the image the screen is about.
163
163
 
164
+ On the web, it's the image's `fetchpriority`.
165
+
164
166
  ---
165
167
 
166
168
  ##### `source.cache`
@@ -365,6 +367,22 @@ How the image is filtered when it's drawn smaller or larger than its size (like
365
367
 
366
368
  ---
367
369
 
370
+ #### `loading`
371
+
372
+ **Type:** `'lazy' | 'eager'` · **Default:** `'lazy'` · Web only
373
+
374
+ Whether the image loads as it nears the screen (`'lazy'`) or at once (`'eager'`).
375
+
376
+ ---
377
+
378
+ #### `sizes`
379
+
380
+ **Type:** `string` · **Default:** `'auto, 100vw'` · Web only
381
+
382
+ With several sources, the width the image is shown at, as the image's [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#sizes), so the browser loads the size that fits. The default is the width the image is laid out at, which needs `loading="lazy"` (otherwise, or in browsers without it, the window's width).
383
+
384
+ ---
385
+
368
386
  #### `paused`
369
387
 
370
388
  **Type:** `boolean`
@@ -932,14 +950,15 @@ The app needs access to the photo library, which it has if it got the url from t
932
950
 
933
951
  ## Web
934
952
 
935
- FastImage works on the web with [react-native-web](https://necolas.github.io/react-native-web/). Bundlers pick the web version through the package's `browser` field, or the `.web.js` files next to the native ones.
953
+ FastImage works on the web with [react-native-web](https://necolas.github.io/react-native-web/) and React 19 or later. Bundlers pick the web version through the package's `browser` field, or the `.web.js` files next to the native ones.
936
954
 
937
955
  Support is minimal:
938
956
 
939
- - Props that work: `source` (a `uri`, a `require()`d image, or several sizes, with `thumbhash` and `blurDataUri`), `placeholder` (and `defaultSource`), `objectFit`, `objectPosition`, `tintColor`, `blurRadius`, `style`, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
940
- - Ignored: `source.headers` (a browser can't send them for an image), `source.priority`, `source.cache`, `source.cacheKey`, `source.memoryCache`, `source.diskCache`, `recyclingKey`, `loop`, `imageRendering`, `paused`, `transition`, `downsample` and `onProgress`.
957
+ - Props that work: `source` (a `uri`, a `require()`d image, or several sizes, with `thumbhash` and `blurDataUri`), `placeholder` (and `defaultSource`), `objectFit`, `objectPosition`, `tintColor`, `blurRadius`, `style`, `source.priority`, the web-only `loading` and `sizes`, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
958
+ - Ignored: `source.headers` (a browser can't send them for an image), `source.cache`, `source.cacheKey`, `source.memoryCache`, `source.diskCache`, `recyclingKey`, `loop`, `imageRendering`, `paused`, `transition`, `downsample` and `onProgress`.
941
959
  - The image is an `<img>` element that fills the view inside its borders, with `objectFit` and `objectPosition` as its CSS `object-fit` and `object-position`. `tintColor` is an SVG filter and `blurRadius` a CSS `blur()`, and the placeholder is another `<img>`, under the image until it has loaded. A new `source` gets a new `<img>`, so the previous image doesn't stay while it loads (with several sizes, the browser shows it until the new one has loaded).
942
- - Several sizes are shown with a lazily loaded `<img>` whose `srcset` lists them, with `sizes="auto, 100vw"`: the browser loads the one for the width the image is shown at, in device pixels (usually the smallest that's at least as wide). Browsers that don't support `sizes="auto"` use the viewport's width.
960
+ - Images load lazily unless [`loading`](#loading) is `'eager'`, and `source.priority` `'high'` and `'low'` are the `<img>`'s `fetchpriority`.
961
+ - Several sizes are shown with an `<img>` whose `srcset` lists them, with `sizes="auto, 100vw"` (or [`sizes`](#sizes)): the browser loads the one for the width the image is shown at, in device pixels (usually the smallest that's at least as wide). Browsers that don't support `sizes="auto"`, or an image with `loading="eager"`, use the viewport's width.
943
962
  - `FastImage.preload` loads the images into the browser's cache and resolves with a result per source. `clearMemoryCache`, `clearDiskCache` and `configureCache` resolve without doing anything (the browser manages its cache), and `getCachePath` and `writeToCache` resolve with `{ ok: false, error: 'Not supported on the web' }`.
944
963
 
945
964
  ### ThumbHashes on the web
package/dist/index.cjs.js CHANGED
@@ -347,7 +347,7 @@ function builtInTransition() {
347
347
  return resolveTransition(react_native.Platform.OS === "android", NO_TRANSITION);
348
348
  }
349
349
  let defaultTransition;
350
- function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, forwardedRef, ...viewProps }) {
350
+ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, loading: _loading, sizes: _sizes, forwardedRef, ...viewProps }) {
351
351
  var _props$recyclingKey, _defaultTransition;
352
352
  const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
353
353
  if (__DEV__ && fallback !== void 0) warnOnce("fallback", "react-native-fast-image: `fallback` was removed in 10.0, and does nothing: images always load natively. Remove it, or use React Native's Image for those images.", "warn");
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
149
149
  recyclingKey?: ?string,
150
150
  loop?: ?(boolean | number),
151
151
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
+ loading?: ?('lazy' | 'eager'),
153
+ sizes?: ?string,
152
154
  paused?: ?boolean,
153
155
  transition?: ?(boolean | number | Transition),
154
156
  downsample?: ?boolean,
@@ -278,16 +278,12 @@ function dataUri(value) {
278
278
  }
279
279
  //#endregion
280
280
  //#region src/index.web.tsx
281
- var _useId;
282
281
  const notSupported = {
283
282
  ok: false,
284
283
  error: "Not supported on the web"
285
284
  };
286
- let ids = 0;
287
- const useCounterId = () => (0, react.useState)(() => `${++ids}`)[0];
288
- const useSvgId = (_useId = react.default.useId) !== null && _useId !== void 0 ? _useId : useCounterId;
289
285
  const useClientLayoutEffect = globalThis.document === void 0 ? react.useEffect : react.useLayoutEffect;
290
- function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
286
+ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loading, sizes, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
291
287
  var _resolveUri;
292
288
  if (typeof __DEV__ !== "undefined" && __DEV__) warnIfChildren(children);
293
289
  const { accessibilityLabel, "aria-label": ariaLabel, resizeMode: _resizeMode, ...viewProps } = props;
@@ -297,12 +293,13 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
297
293
  const src = sized ? sized.src : resolveUri(source);
298
294
  const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
299
295
  const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
296
+ const priority = sourcePriority(source);
300
297
  const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
301
298
  const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
302
299
  const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
303
300
  const [result, setResult] = (0, react.useState)();
304
301
  const ok = result && result.key === key ? result.ok : void 0;
305
- const svgId = useSvgId().replace(/[^\w-]/g, "");
302
+ const svgId = (0, react.useId)().replace(/[^\w-]/g, "");
306
303
  const tintId = `fast-image-tint-${svgId}`;
307
304
  const blurId = `fast-image-blur-${svgId}`;
308
305
  const fit = resolveObjectFit(objectFit, style);
@@ -322,6 +319,8 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
322
319
  }, defaultUri && showDefault ? /* @__PURE__ */ react.default.createElement(Picture, {
323
320
  key: `default ${defaultUri}`,
324
321
  src: defaultUri,
322
+ loading,
323
+ priority,
325
324
  fit: blurUri && (fit === "scale-down" || fit === "none") ? "contain" : fit,
326
325
  position,
327
326
  filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
@@ -330,7 +329,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
330
329
  key: several ? "sizes" : key,
331
330
  src,
332
331
  srcSet,
333
- lazy: !!several,
332
+ sizes,
333
+ loading,
334
+ priority,
334
335
  fit,
335
336
  position,
336
337
  filter,
@@ -375,6 +376,14 @@ function thumbhashUri(hash) {
375
376
  if (decode) return decode(hash);
376
377
  if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("thumbhash-web", "react-native-fast-image: on the web, a `thumbhash` shows only with `import 'react-native-fast-image/thumbhash'` (it decodes them). Or make it a `blurDataUri` before it reaches the browser (e.g. on your server) with that module's thumbHashToBlurDataUri, which needs no JavaScript in the browser.", "warn");
377
378
  }
379
+ function sourcePriority(source) {
380
+ const values = (Array.isArray(source) ? source : [source]).map((entry) => entry && typeof entry === "object" ? entry.priority : void 0);
381
+ return [
382
+ "high",
383
+ "normal",
384
+ "low"
385
+ ].find((priority) => values.includes(priority));
386
+ }
378
387
  function shared(source, key, preview) {
379
388
  const values = (Array.isArray(source) ? source : [source]).map((entry) => {
380
389
  var _ref;
@@ -429,7 +438,7 @@ const fill = {
429
438
  width: "100%",
430
439
  height: "100%"
431
440
  };
432
- function Picture({ src, srcSet, lazy, fit, position, filter, hidden, alt, onLoad, onError }) {
441
+ function Picture({ src, srcSet, sizes, loading, priority, fit, position, filter, hidden, alt, onLoad, onError }) {
433
442
  const image = (0, react.useRef)(null);
434
443
  const mounted = (0, react.useRef)(true);
435
444
  const handled = (0, react.useRef)(void 0);
@@ -466,8 +475,9 @@ function Picture({ src, srcSet, lazy, fit, position, filter, hidden, alt, onLoad
466
475
  ref: image,
467
476
  src,
468
477
  srcSet,
469
- sizes: srcSet ? "auto, 100vw" : void 0,
470
- loading: lazy ? "lazy" : void 0,
478
+ sizes: srcSet ? sizes !== null && sizes !== void 0 ? sizes : "auto, 100vw" : void 0,
479
+ loading: loading === "eager" ? void 0 : "lazy",
480
+ fetchPriority: priority === "high" || priority === "low" ? priority : void 0,
471
481
  alt,
472
482
  draggable: false,
473
483
  style: {
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
149
149
  recyclingKey?: ?string,
150
150
  loop?: ?(boolean | number),
151
151
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
+ loading?: ?('lazy' | 'eager'),
153
+ sizes?: ?string,
152
154
  paused?: ?boolean,
153
155
  transition?: ?(boolean | number | Transition),
154
156
  downsample?: ?boolean,
package/dist/index.d.ts CHANGED
@@ -35,6 +35,8 @@ export type Source = {
35
35
  * - `'normal'`: the default.
36
36
  * - `'high'`: e.g. the image the screen is about.
37
37
  *
38
+ * On the web, it's the image's `fetchpriority`.
39
+ *
38
40
  * @default 'normal'
39
41
  */
40
42
  priority?: Priority;
@@ -452,6 +454,25 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
452
454
  * @default 'auto'
453
455
  */
454
456
  imageRendering?: 'auto' | 'smooth' | 'pixelated';
457
+ /**
458
+ * Whether the image loads as it nears the screen (`'lazy'`) or at once
459
+ * (`'eager'`).
460
+ *
461
+ * @default 'lazy'
462
+ * @platform web
463
+ */
464
+ loading?: 'lazy' | 'eager';
465
+ /**
466
+ * With several sources, the width the image is shown at, as the image's
467
+ * [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#sizes),
468
+ * so the browser loads the size that fits. The default is the width the
469
+ * image is laid out at, which needs `loading="lazy"` (otherwise, or in
470
+ * browsers without it, the window's width).
471
+ *
472
+ * @default 'auto, 100vw'
473
+ * @platform web
474
+ */
475
+ sizes?: string;
455
476
  /**
456
477
  * Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on
457
478
  * the frame it's showing; `false` plays it again from there
package/dist/index.js CHANGED
@@ -320,7 +320,7 @@ function builtInTransition() {
320
320
  return resolveTransition(Platform.OS === "android", NO_TRANSITION);
321
321
  }
322
322
  let defaultTransition;
323
- function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, forwardedRef, ...viewProps }) {
323
+ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, loading: _loading, sizes: _sizes, forwardedRef, ...viewProps }) {
324
324
  var _props$recyclingKey, _defaultTransition;
325
325
  const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
326
326
  if (__DEV__ && fallback !== void 0) warnOnce("fallback", "react-native-fast-image: `fallback` was removed in 10.0, and does nothing: images always load natively. Remove it, or use React Native's Image for those images.", "warn");
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
149
149
  recyclingKey?: ?string,
150
150
  loop?: ?(boolean | number),
151
151
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
+ loading?: ?('lazy' | 'eager'),
153
+ sizes?: ?string,
152
154
  paused?: ?boolean,
153
155
  transition?: ?(boolean | number | Transition),
154
156
  downsample?: ?boolean,
package/dist/index.web.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { Children, forwardRef, memo, useEffect, useLayoutEffect, useRef, useState } from "react";
1
+ import React, { Children, forwardRef, memo, useEffect, useId, useLayoutEffect, 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
@@ -251,16 +251,12 @@ function dataUri(value) {
251
251
  }
252
252
  //#endregion
253
253
  //#region src/index.web.tsx
254
- var _useId;
255
254
  const notSupported = {
256
255
  ok: false,
257
256
  error: "Not supported on the web"
258
257
  };
259
- let ids = 0;
260
- const useCounterId = () => useState(() => `${++ids}`)[0];
261
- const useSvgId = (_useId = React.useId) !== null && _useId !== void 0 ? _useId : useCounterId;
262
258
  const useClientLayoutEffect = globalThis.document === void 0 ? useEffect : useLayoutEffect;
263
- function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
259
+ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loading, sizes, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
264
260
  var _resolveUri;
265
261
  if (typeof __DEV__ !== "undefined" && __DEV__) warnIfChildren(children);
266
262
  const { accessibilityLabel, "aria-label": ariaLabel, resizeMode: _resizeMode, ...viewProps } = props;
@@ -270,12 +266,13 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
270
266
  const src = sized ? sized.src : resolveUri(source);
271
267
  const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
272
268
  const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
269
+ const priority = sourcePriority(source);
273
270
  const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
274
271
  const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
275
272
  const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
276
273
  const [result, setResult] = useState();
277
274
  const ok = result && result.key === key ? result.ok : void 0;
278
- const svgId = useSvgId().replace(/[^\w-]/g, "");
275
+ const svgId = useId().replace(/[^\w-]/g, "");
279
276
  const tintId = `fast-image-tint-${svgId}`;
280
277
  const blurId = `fast-image-blur-${svgId}`;
281
278
  const fit = resolveObjectFit(objectFit, style);
@@ -295,6 +292,8 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
295
292
  }, defaultUri && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
296
293
  key: `default ${defaultUri}`,
297
294
  src: defaultUri,
295
+ loading,
296
+ priority,
298
297
  fit: blurUri && (fit === "scale-down" || fit === "none") ? "contain" : fit,
299
298
  position,
300
299
  filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
@@ -303,7 +302,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
303
302
  key: several ? "sizes" : key,
304
303
  src,
305
304
  srcSet,
306
- lazy: !!several,
305
+ sizes,
306
+ loading,
307
+ priority,
307
308
  fit,
308
309
  position,
309
310
  filter,
@@ -348,6 +349,14 @@ function thumbhashUri(hash) {
348
349
  if (decode) return decode(hash);
349
350
  if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("thumbhash-web", "react-native-fast-image: on the web, a `thumbhash` shows only with `import 'react-native-fast-image/thumbhash'` (it decodes them). Or make it a `blurDataUri` before it reaches the browser (e.g. on your server) with that module's thumbHashToBlurDataUri, which needs no JavaScript in the browser.", "warn");
350
351
  }
352
+ function sourcePriority(source) {
353
+ const values = (Array.isArray(source) ? source : [source]).map((entry) => entry && typeof entry === "object" ? entry.priority : void 0);
354
+ return [
355
+ "high",
356
+ "normal",
357
+ "low"
358
+ ].find((priority) => values.includes(priority));
359
+ }
351
360
  function shared(source, key, preview) {
352
361
  const values = (Array.isArray(source) ? source : [source]).map((entry) => {
353
362
  var _ref;
@@ -402,7 +411,7 @@ const fill = {
402
411
  width: "100%",
403
412
  height: "100%"
404
413
  };
405
- function Picture({ src, srcSet, lazy, fit, position, filter, hidden, alt, onLoad, onError }) {
414
+ function Picture({ src, srcSet, sizes, loading, priority, fit, position, filter, hidden, alt, onLoad, onError }) {
406
415
  const image = useRef(null);
407
416
  const mounted = useRef(true);
408
417
  const handled = useRef(void 0);
@@ -439,8 +448,9 @@ function Picture({ src, srcSet, lazy, fit, position, filter, hidden, alt, onLoad
439
448
  ref: image,
440
449
  src,
441
450
  srcSet,
442
- sizes: srcSet ? "auto, 100vw" : void 0,
443
- loading: lazy ? "lazy" : void 0,
451
+ sizes: srcSet ? sizes !== null && sizes !== void 0 ? sizes : "auto, 100vw" : void 0,
452
+ loading: loading === "eager" ? void 0 : "lazy",
453
+ fetchPriority: priority === "high" || priority === "low" ? priority : void 0,
444
454
  alt,
445
455
  draggable: false,
446
456
  style: {
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
149
149
  recyclingKey?: ?string,
150
150
  loop?: ?(boolean | number),
151
151
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
+ loading?: ?('lazy' | 'eager'),
153
+ sizes?: ?string,
152
154
  paused?: ?boolean,
153
155
  transition?: ?(boolean | number | Transition),
154
156
  downsample?: ?boolean,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "10.2.0",
3
+ "version": "10.3.0",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",
package/src/index.js.flow CHANGED
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
149
149
  recyclingKey?: ?string,
150
150
  loop?: ?(boolean | number),
151
151
  imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
152
+ loading?: ?('lazy' | 'eager'),
153
+ sizes?: ?string,
152
154
  paused?: ?boolean,
153
155
  transition?: ?(boolean | number | Transition),
154
156
  downsample?: ?boolean,
package/src/index.tsx CHANGED
@@ -54,6 +54,8 @@ export type Source = {
54
54
  * - `'normal'`: the default.
55
55
  * - `'high'`: e.g. the image the screen is about.
56
56
  *
57
+ * On the web, it's the image's `fetchpriority`.
58
+ *
57
59
  * @default 'normal'
58
60
  */
59
61
  priority?: Priority
@@ -474,6 +476,25 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
474
476
  * @default 'auto'
475
477
  */
476
478
  imageRendering?: 'auto' | 'smooth' | 'pixelated'
479
+ /**
480
+ * Whether the image loads as it nears the screen (`'lazy'`) or at once
481
+ * (`'eager'`).
482
+ *
483
+ * @default 'lazy'
484
+ * @platform web
485
+ */
486
+ loading?: 'lazy' | 'eager'
487
+ /**
488
+ * With several sources, the width the image is shown at, as the image's
489
+ * [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#sizes),
490
+ * so the browser loads the size that fits. The default is the width the
491
+ * image is laid out at, which needs `loading="lazy"` (otherwise, or in
492
+ * browsers without it, the window's width).
493
+ *
494
+ * @default 'auto, 100vw'
495
+ * @platform web
496
+ */
497
+ sizes?: string
477
498
  /**
478
499
  * Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on
479
500
  * the frame it's showing; `false` plays it again from there
@@ -811,6 +832,9 @@ function FastImageBase({
811
832
  objectPosition,
812
833
  loop,
813
834
  transition,
835
+ // Web only.
836
+ loading: _loading,
837
+ sizes: _sizes,
814
838
  forwardedRef,
815
839
  ...viewProps
816
840
  }: FastImageProps & { forwardedRef: React.Ref<any> }) {
package/src/index.web.tsx CHANGED
@@ -11,15 +11,17 @@
11
11
  //
12
12
  // Works: source (uri, require(), or several sizes: see sizedSources, and
13
13
  // thumbhash and blurDataUri), placeholder (and defaultSource), objectFit,
14
- // objectPosition, tintColor, blurRadius, style,
15
- // onLoadStart, onLoad, onError, onLoadEnd, and View props (testID,
16
- // accessibility, onLayout, pointerEvents). The native-only props (cache,
17
- // priority, headers, transition, downsample, loop, paused, imageRendering,
18
- // recyclingKey) and onProgress are ignored.
14
+ // objectPosition, tintColor, blurRadius, style, source.priority (as
15
+ // fetchpriority), the web-only loading and sizes, onLoadStart, onLoad,
16
+ // onError, onLoadEnd, and View props (testID, accessibility, onLayout,
17
+ // pointerEvents). The native-only props (cache, headers, transition,
18
+ // downsample, loop, paused, imageRendering, recyclingKey) and onProgress
19
+ // are ignored.
19
20
  import React, {
20
21
  forwardRef,
21
22
  memo,
22
23
  useEffect,
24
+ useId,
23
25
  useLayoutEffect,
24
26
  useRef,
25
27
  useState,
@@ -40,6 +42,7 @@ import type {
40
42
  FastImageStaticProperties,
41
43
  ObjectFit,
42
44
  PreloadResult,
45
+ Priority,
43
46
  Source,
44
47
  SourcePlaceholder,
45
48
  } from './index'
@@ -52,15 +55,6 @@ const notSupported: CachePathResult = {
52
55
  error: 'Not supported on the web',
53
56
  }
54
57
 
55
- // An id that's the same on the server and in the browser: React's useId,
56
- // from React 18 (this package's React types are older), without the
57
- // characters an SVG id can't have (:r0: in React 18, «r0» in React 19). On
58
- // React 17, a counter's (which can differ from the server's).
59
- let ids = 0
60
- const useCounterId = () => useState(() => `${++ids}`)[0]
61
- const useSvgId: () => string =
62
- (React as unknown as { useId?: () => string }).useId ?? useCounterId
63
-
64
58
  // A layout effect in the browser (it runs before the image can show), and an
65
59
  // effect when rendering on a server, where layout effects warn.
66
60
  const useClientLayoutEffect =
@@ -86,6 +80,8 @@ function FastImageBase({
86
80
  children,
87
81
  objectFit,
88
82
  objectPosition,
83
+ loading,
84
+ sizes,
89
85
  forwardedRef,
90
86
  // On the wrapper, the view the layout and touches are for here (on
91
87
  // native, FastImage's only view).
@@ -125,6 +121,7 @@ function FastImageBase({
125
121
  // one doesn't stay while it loads; with several sizes, the <img> stays,
126
122
  // and the browser shows the previous image until the new one has loaded.
127
123
  const key = srcSet ?? src
124
+ const priority = sourcePriority(source)
128
125
  // The source's blurDataUri, or its ThumbHash made into one (with
129
126
  // `react-native-fast-image/thumbhash`), shows blurred (see PreviewBlur)
130
127
  // in place of `placeholder` (or `placeholder`'s, for a source without its
@@ -144,7 +141,7 @@ function FastImageBase({
144
141
  const ok = result && result.key === key ? result.ok : undefined
145
142
  // The same on the server and in the browser. React's ids have characters
146
143
  // an SVG id can't (:r0: in React 18, «r0» in React 19).
147
- const svgId = useSvgId().replace(/[^\w-]/g, '')
144
+ const svgId = useId().replace(/[^\w-]/g, '')
148
145
  const tintId = `fast-image-tint-${svgId}`
149
146
  const blurId = `fast-image-blur-${svgId}`
150
147
  // objectFit (the prop, then style's).
@@ -179,6 +176,9 @@ function FastImageBase({
179
176
  <Picture
180
177
  key={`default ${defaultUri}`}
181
178
  src={defaultUri}
179
+ // Loaded when the image is (a data uri needs no request).
180
+ loading={loading}
181
+ priority={priority}
182
182
  // A source's placeholder stands for an image whose size
183
183
  // isn't known yet: as contain for scale-down and none,
184
184
  // which show an image at its own size.
@@ -210,7 +210,9 @@ function FastImageBase({
210
210
  key={several ? 'sizes' : key}
211
211
  src={src}
212
212
  srcSet={srcSet}
213
- lazy={!!several}
213
+ sizes={sizes}
214
+ loading={loading}
215
+ priority={priority}
214
216
  fit={fit}
215
217
  position={position}
216
218
  filter={filter}
@@ -257,6 +259,20 @@ function thumbhashUri(hash: string | undefined): string | undefined {
257
259
  return undefined
258
260
  }
259
261
 
262
+ // A source's priority: one source's, or the highest of several sizes'.
263
+ function sourcePriority(
264
+ source: FastImageProps['source'],
265
+ ): Priority | undefined {
266
+ const values = (Array.isArray(source) ? source : [source]).map((entry) =>
267
+ entry && typeof entry === 'object'
268
+ ? (entry as Source).priority
269
+ : undefined,
270
+ )
271
+ return (['high', 'normal', 'low'] as const).find((priority) =>
272
+ values.includes(priority),
273
+ )
274
+ }
275
+
260
276
  // The thumbhash or blurDataUri a source shows: its own, or `placeholder`'s
261
277
  // (preview) if it has neither. With several sources, the one they all show.
262
278
  function shared(
@@ -321,9 +337,9 @@ type PackagerAsset = {
321
337
  // Several sizes of an image, as a srcset (each size's width, times its
322
338
  // scale), from which the browser loads the one for the image's width in
323
339
  // device pixels, usually the smallest at least as wide, as expo-image does.
324
- // sizes="auto" has the browser use the width the image is laid out at; it
325
- // only applies to lazy images, and browsers without it use the next value,
326
- // 100vw (the viewport's width). src is the largest, for browsers without
340
+ // sizes="auto" (the default sizes) has the browser use the width the image
341
+ // is laid out at; it only applies to lazy images, and otherwise, or in
342
+ // browsers without it, the next value, 100vw (the viewport's width). src is the largest, for browsers without
327
343
  // srcset.
328
344
  function sizedSources(sources: Source[]) {
329
345
  const widths = sources
@@ -395,7 +411,9 @@ const fill = {
395
411
  function Picture({
396
412
  src,
397
413
  srcSet,
398
- lazy,
414
+ sizes,
415
+ loading,
416
+ priority,
399
417
  fit,
400
418
  position,
401
419
  filter,
@@ -406,8 +424,11 @@ function Picture({
406
424
  }: {
407
425
  src: string
408
426
  srcSet?: string
409
- // Several sizes load lazily, for sizes="auto" (see sizedSources).
410
- lazy?: boolean
427
+ // The props (see sizedSources for sizes).
428
+ sizes?: string
429
+ loading?: 'lazy' | 'eager'
430
+ // source.priority, as the <img>'s fetchpriority.
431
+ priority?: Priority
411
432
  fit: ObjectFit
412
433
  // objectPosition, as CSS.
413
434
  position: string
@@ -478,8 +499,12 @@ function Picture({
478
499
  ref={image}
479
500
  src={src}
480
501
  srcSet={srcSet}
481
- sizes={srcSet ? 'auto, 100vw' : undefined}
482
- loading={lazy ? 'lazy' : undefined}
502
+ sizes={srcSet ? (sizes ?? 'auto, 100vw') : undefined}
503
+ loading={loading === 'eager' ? undefined : 'lazy'}
504
+ // For 'high' and 'low' (the browser's own for 'normal').
505
+ fetchPriority={
506
+ priority === 'high' || priority === 'low' ? priority : undefined
507
+ }
483
508
  alt={alt}
484
509
  draggable={false}
485
510
  style={{