react-native-fast-image 10.4.0 → 10.6.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 +17 -6
- package/android/src/main/java/com/fastimage/FastImageViewConverter.java +1 -1
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +18 -12
- package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +231 -32
- package/dist/index.cjs.js +15 -27
- package/dist/index.cjs.js.flow +9 -2
- package/dist/index.cjs.web.js +41 -8
- package/dist/index.cjs.web.js.flow +9 -2
- package/dist/index.d.ts +15 -5
- package/dist/index.js +15 -27
- package/dist/index.js.flow +9 -2
- package/dist/index.web.js +41 -8
- package/dist/index.web.js.flow +9 -2
- package/dist/placeholders.d.ts +2 -1
- package/dist/specs/FastImageViewNativeComponent.d.ts +0 -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 +187 -33
- package/ios/FastImage/FFFastImageViewComponentView.mm +78 -13
- package/package.json +1 -1
- package/src/index.js.flow +9 -2
- package/src/index.tsx +59 -71
- package/src/index.web.tsx +78 -15
- package/src/placeholders.ts +26 -9
- package/src/specs/FastImageViewNativeComponent.ts +10 -11
package/dist/index.cjs.web.js
CHANGED
|
@@ -268,13 +268,18 @@ function cssPosition({ x, y }) {
|
|
|
268
268
|
}
|
|
269
269
|
//#endregion
|
|
270
270
|
//#region src/placeholders.ts
|
|
271
|
+
function placeholderSource(value) {
|
|
272
|
+
const uri = typeof value === "string" ? value : value && typeof value === "object" ? value.uri : void 0;
|
|
273
|
+
if (typeof uri !== "string" || !/^https?:/i.test(uri)) return void 0;
|
|
274
|
+
return typeof value === "string" ? { uri } : value;
|
|
275
|
+
}
|
|
271
276
|
function hasSourcePlaceholder(value) {
|
|
272
277
|
return !!value && typeof value === "object" && ("thumbhash" in value || "blurDataUri" in value);
|
|
273
278
|
}
|
|
274
279
|
function dataUri(value) {
|
|
275
280
|
if (typeof value !== "string" || value === "") return void 0;
|
|
276
281
|
if (/^data:/i.test(value)) return value;
|
|
277
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`
|
|
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");
|
|
278
283
|
}
|
|
279
284
|
//#endregion
|
|
280
285
|
//#region src/index.web.tsx
|
|
@@ -284,7 +289,7 @@ const notSupported = {
|
|
|
284
289
|
};
|
|
285
290
|
const useClientLayoutEffect = globalThis.document === void 0 ? react.useEffect : react.useLayoutEffect;
|
|
286
291
|
function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loading, sizes, dynamicRangeLimit, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
|
|
287
|
-
var _resolveUri;
|
|
292
|
+
var _resolveUri, _placeholderSource;
|
|
288
293
|
if (typeof __DEV__ !== "undefined" && __DEV__) warnIfChildren(children);
|
|
289
294
|
const { accessibilityLabel, "aria-label": ariaLabel, resizeMode: _resizeMode, ...viewProps } = props;
|
|
290
295
|
const label = ariaLabel || accessibilityLabel || void 0;
|
|
@@ -296,7 +301,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
296
301
|
const priority = sourcePriority(source);
|
|
297
302
|
const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
|
|
298
303
|
const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
|
|
299
|
-
const
|
|
304
|
+
const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
|
|
305
|
+
const [failedPlaceholder, setFailedPlaceholder] = (0, react.useState)();
|
|
306
|
+
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : urlPlaceholder ? void 0 : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
300
307
|
const [result, setResult] = (0, react.useState)();
|
|
301
308
|
const ok = result && result.key === key ? result.ok : void 0;
|
|
302
309
|
const svgId = (0, react.useId)().replace(/[^\w-]/g, "");
|
|
@@ -305,7 +312,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
305
312
|
const fit = resolveObjectFit(objectFit, style);
|
|
306
313
|
const position = cssPosition(objectPositionOrCentered(objectPosition));
|
|
307
314
|
const filter = [blurRadius ? `blur(${blurRadius}px)` : "", tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") || void 0;
|
|
308
|
-
const showDefault =
|
|
315
|
+
const showDefault = ok !== true;
|
|
309
316
|
useClientLayoutEffect(() => {
|
|
310
317
|
if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
|
|
311
318
|
}, [key]);
|
|
@@ -326,6 +333,18 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
326
333
|
position,
|
|
327
334
|
filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
|
|
328
335
|
alt: key ? "" : label !== null && label !== void 0 ? label : ""
|
|
336
|
+
}) : null, urlPlaceholder && showDefault ? /* @__PURE__ */ react.default.createElement(Picture, {
|
|
337
|
+
key: `placeholder ${urlPlaceholder}`,
|
|
338
|
+
src: urlPlaceholder,
|
|
339
|
+
loading,
|
|
340
|
+
priority: "high",
|
|
341
|
+
dynamicRangeLimit,
|
|
342
|
+
fit: fit === "scale-down" || fit === "none" ? "contain" : fit,
|
|
343
|
+
position,
|
|
344
|
+
filter: tintColor ? `url(#${tintId})` : void 0,
|
|
345
|
+
hidden: failedPlaceholder === urlPlaceholder,
|
|
346
|
+
onError: () => setFailedPlaceholder(urlPlaceholder),
|
|
347
|
+
alt: ""
|
|
329
348
|
}) : null, key && src ? /* @__PURE__ */ react.default.createElement(Picture, {
|
|
330
349
|
key: several ? "sizes" : key,
|
|
331
350
|
src,
|
|
@@ -397,10 +416,9 @@ function shared(source, key, preview) {
|
|
|
397
416
|
function resolveUri(source) {
|
|
398
417
|
let uri;
|
|
399
418
|
if (typeof source === "number") {
|
|
400
|
-
var _asset$scales$;
|
|
401
419
|
const asset = (0, react_native_web_dist_modules_AssetRegistry.getAssetByID)(source);
|
|
402
420
|
if (!asset) return void 0;
|
|
403
|
-
const scale =
|
|
421
|
+
const scale = assetScale(asset);
|
|
404
422
|
const suffix = scale !== 1 ? `@${scale}x` : "";
|
|
405
423
|
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`;
|
|
406
424
|
} else if (typeof source === "string") uri = source;
|
|
@@ -409,9 +427,24 @@ function resolveUri(source) {
|
|
|
409
427
|
const svg = uri === null || uri === void 0 ? void 0 : uri.match(/^(data:image\/svg\+xml;utf8,)(.*)/);
|
|
410
428
|
return svg ? svg[1] + encodeURIComponent(svg[2]) : uri || void 0;
|
|
411
429
|
}
|
|
412
|
-
function
|
|
430
|
+
function assetScale(asset) {
|
|
431
|
+
var _asset$scales$;
|
|
432
|
+
return asset.scales.reduce((best, scale) => Math.abs(scale - react_native.PixelRatio.get()) < Math.abs(best - react_native.PixelRatio.get()) ? scale : best, (_asset$scales$ = asset.scales[0]) !== null && _asset$scales$ !== void 0 ? _asset$scales$ : 1);
|
|
433
|
+
}
|
|
434
|
+
function sizedSource(source) {
|
|
435
|
+
if (typeof source !== "number") return source;
|
|
436
|
+
const asset = (0, react_native_web_dist_modules_AssetRegistry.getAssetByID)(source);
|
|
437
|
+
return asset ? {
|
|
438
|
+
uri: resolveUri(source),
|
|
439
|
+
width: asset.width,
|
|
440
|
+
height: asset.height,
|
|
441
|
+
scale: assetScale(asset)
|
|
442
|
+
} : void 0;
|
|
443
|
+
}
|
|
444
|
+
function sizedSources(entries) {
|
|
413
445
|
var _largest$uri, _sources$find;
|
|
414
|
-
const
|
|
446
|
+
const sources = entries.map(sizedSource);
|
|
447
|
+
const widths = sources.filter((source) => !!(source === null || source === void 0 ? void 0 : source.uri) && !!source.width).map((source) => {
|
|
415
448
|
var _source$width, _source$scale;
|
|
416
449
|
return {
|
|
417
450
|
uri: source.uri,
|
|
@@ -110,6 +110,13 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
|
|
|
110
110
|
export type PreloadFn = (
|
|
111
111
|
sources: Array<FastImageSource>,
|
|
112
112
|
) => Promise<Array<PreloadResult>>
|
|
113
|
+
export type PlaceholderSource = {
|
|
114
|
+
uri?: string,
|
|
115
|
+
headers?: Object,
|
|
116
|
+
cacheKey?: string,
|
|
117
|
+
cache?: CacheControls,
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
export type SourcePlaceholder = {
|
|
114
121
|
thumbhash?: string,
|
|
115
122
|
blurDataUri?: string,
|
|
@@ -138,8 +145,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
138
145
|
onLoadStart?: ?() => void,
|
|
139
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
140
147
|
|
|
141
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
142
|
-
placeholder?: ?(number | string | SourcePlaceholder),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
|
+
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
143
150
|
// Deprecated: use placeholder, which is the same.
|
|
144
151
|
defaultSource?: ?number,
|
|
145
152
|
|
package/dist/index.d.ts
CHANGED
|
@@ -154,6 +154,8 @@ export type Source = {
|
|
|
154
154
|
};
|
|
155
155
|
/** A `thumbhash` or `blurDataUri` as `placeholder`. */
|
|
156
156
|
export type SourcePlaceholder = Pick<Source, 'thumbhash' | 'blurDataUri'>;
|
|
157
|
+
/** A url as `placeholder`, loaded like a source (see `placeholder`). */
|
|
158
|
+
export type PlaceholderSource = Pick<Source, 'uri' | 'headers' | 'cacheKey' | 'cache'>;
|
|
157
159
|
/** How the image fades in: see [`transition`](#transition). */
|
|
158
160
|
export type Transition = {
|
|
159
161
|
/**
|
|
@@ -329,9 +331,10 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
329
331
|
* - `FastImage.preload`, `getCachePath` and `writeToCache` take one size:
|
|
330
332
|
* pass the one a view will show. An array fails with
|
|
331
333
|
* `{ ok: false, error }`.
|
|
334
|
+
* - A `require()`d image can be one of the sizes: its size is the file's.
|
|
332
335
|
* - An array of one is the same as that source.
|
|
333
336
|
*/
|
|
334
|
-
source?: Source | Source
|
|
337
|
+
source?: Source | Array<Source | ImageRequireSource> | ImageRequireSource;
|
|
335
338
|
/**
|
|
336
339
|
* A placeholder shown while the image loads, and if it fails to load:
|
|
337
340
|
*
|
|
@@ -343,19 +346,26 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
343
346
|
* `placeholder={{ thumbhash }}`.
|
|
344
347
|
* - A tiny image as a data uri, shown blurred:
|
|
345
348
|
* `placeholder={{ blurDataUri }}`.
|
|
349
|
+
* - A url, loaded and cached like a source:
|
|
350
|
+
* `placeholder="https://example.com/placeholder.png"`, or a source with
|
|
351
|
+
* `headers` or a `cacheKey`: `placeholder={{ uri, headers }}`.
|
|
352
|
+
* - A url only shown if it's already cached, e.g. a list's thumbnail while
|
|
353
|
+
* the full-size image loads: `placeholder={{ uri, cache: 'cacheOnly' }}`.
|
|
346
354
|
*
|
|
347
355
|
* A source's own `thumbhash` or `blurDataUri` is shown instead, if it has
|
|
348
|
-
* one.
|
|
356
|
+
* one; a url placeholder shows over it once it has loaded.
|
|
349
357
|
*
|
|
350
358
|
* Notes:
|
|
351
359
|
*
|
|
352
|
-
* - A url isn't loaded: it's ignored, with a warning in development.
|
|
353
360
|
* - On Android, a `require()`d image doesn't show in debug builds, where
|
|
354
361
|
* the dev server serves it. Release builds show it.
|
|
362
|
+
* - On the web, a url loads like any image, also with `'cacheOnly'`: a
|
|
363
|
+
* browser can't look only in its cache.
|
|
355
364
|
*/
|
|
356
|
-
placeholder?: ImageRequireSource | string | SourcePlaceholder;
|
|
365
|
+
placeholder?: ImageRequireSource | string | SourcePlaceholder | PlaceholderSource;
|
|
357
366
|
/**
|
|
358
|
-
* @deprecated Use `placeholder`, which does the same.
|
|
367
|
+
* @deprecated Use `placeholder`, which does the same. With both,
|
|
368
|
+
* `placeholder` shows.
|
|
359
369
|
*/
|
|
360
370
|
defaultSource?: ImageRequireSource;
|
|
361
371
|
/**
|
package/dist/index.js
CHANGED
|
@@ -234,8 +234,14 @@ var FastImageViewNativeComponent_default = codegenNativeComponent("FastImageView
|
|
|
234
234
|
var NativeFastImageModule_default = TurboModuleRegistry.get("FastImageModule");
|
|
235
235
|
//#endregion
|
|
236
236
|
//#region src/placeholders.ts
|
|
237
|
+
const SCHEME = /^[a-z][a-z\d+.-]*:/i;
|
|
237
238
|
function isImageName(value) {
|
|
238
|
-
return
|
|
239
|
+
return !SCHEME.test(value) && !value.startsWith("/");
|
|
240
|
+
}
|
|
241
|
+
function placeholderSource(value) {
|
|
242
|
+
const uri = typeof value === "string" ? value : value && typeof value === "object" ? value.uri : void 0;
|
|
243
|
+
if (typeof uri !== "string" || !/^https?:/i.test(uri)) return void 0;
|
|
244
|
+
return typeof value === "string" ? { uri } : value;
|
|
239
245
|
}
|
|
240
246
|
function hasSourcePlaceholder(value) {
|
|
241
247
|
return !!value && typeof value === "object" && ("thumbhash" in value || "blurDataUri" in value);
|
|
@@ -243,20 +249,10 @@ function hasSourcePlaceholder(value) {
|
|
|
243
249
|
function dataUri(value) {
|
|
244
250
|
if (typeof value !== "string" || value === "") return void 0;
|
|
245
251
|
if (/^data:/i.test(value)) return value;
|
|
246
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`
|
|
252
|
+
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");
|
|
247
253
|
}
|
|
248
254
|
//#endregion
|
|
249
255
|
//#region src/index.tsx
|
|
250
|
-
function withoutPlaceholders(source) {
|
|
251
|
-
if (!hasSourcePlaceholder(source)) return source;
|
|
252
|
-
const { thumbhash: _thumbhash, blurDataUri: _blurDataUri, ...rest } = source;
|
|
253
|
-
return rest;
|
|
254
|
-
}
|
|
255
|
-
function sourcePlaceholder(source) {
|
|
256
|
-
if (!source || typeof source !== "object") return "";
|
|
257
|
-
const { thumbhash, blurDataUri } = source;
|
|
258
|
-
return thumbhash || dataUri(blurDataUri) || "";
|
|
259
|
-
}
|
|
260
256
|
const resolvePlaceholder = (placeholder) => {
|
|
261
257
|
if (typeof placeholder === "string") {
|
|
262
258
|
if (isImageName(placeholder)) return Platform.OS === "android" ? placeholder : { uri: placeholder };
|
|
@@ -321,25 +317,19 @@ function builtInTransition() {
|
|
|
321
317
|
}
|
|
322
318
|
let defaultTransition;
|
|
323
319
|
function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, loading: _loading, sizes: _sizes, forwardedRef, ...viewProps }) {
|
|
324
|
-
var _props$recyclingKey, _defaultTransition;
|
|
320
|
+
var _placeholderSource, _props$recyclingKey, _defaultTransition;
|
|
325
321
|
const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
|
|
326
322
|
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");
|
|
327
323
|
if (__DEV__) warnIfChildren(children);
|
|
328
324
|
const resolvedTintColor = tintColor != null ? tintColor : fromStyle(style, "tintColor");
|
|
329
325
|
const fit = resolveObjectFit(objectFit, style);
|
|
330
326
|
const position = objectPositionOrCentered(objectPosition);
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
resolvedSource: sources ? void 0 : Image.resolveAssetSource(withoutPlaceholders(single)),
|
|
338
|
-
sources: (sources === null || sources === void 0 ? void 0 : sources.some(hasSourcePlaceholder)) ? sources.map(withoutPlaceholders) : sources,
|
|
339
|
-
sourcePlaceholders: placeholders.some(Boolean) ? placeholders : void 0
|
|
340
|
-
};
|
|
341
|
-
}, [source, shared]);
|
|
342
|
-
const resolvedPlaceholder = resolvePlaceholder(hasSourcePlaceholder(placeholder) ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
327
|
+
const resolvedSource = useMemo(() => {
|
|
328
|
+
for (const entry of [source].flat()) dataUri(entry === null || entry === void 0 ? void 0 : entry.blurDataUri);
|
|
329
|
+
const single = Array.isArray(source) && source.length === 1 ? source[0] : source;
|
|
330
|
+
return Array.isArray(single) ? single.map((entry) => Image.resolveAssetSource(entry)) : Image.resolveAssetSource(single);
|
|
331
|
+
}, [source]);
|
|
332
|
+
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);
|
|
343
333
|
return /* @__PURE__ */ React.createElement(FastImageViewNativeComponent_default, {
|
|
344
334
|
...props,
|
|
345
335
|
ref: forwardedRef,
|
|
@@ -348,8 +338,6 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadSt
|
|
|
348
338
|
loopCount: loopCount(loop),
|
|
349
339
|
...resolveTransition(transition, (_defaultTransition = defaultTransition) !== null && _defaultTransition !== void 0 ? _defaultTransition : builtInTransition()),
|
|
350
340
|
source: resolvedSource,
|
|
351
|
-
sources,
|
|
352
|
-
sourcePlaceholders,
|
|
353
341
|
placeholder: resolvedPlaceholder,
|
|
354
342
|
onFastImageLoadStart: onLoadStart,
|
|
355
343
|
onFastImageProgress: onProgress,
|
package/dist/index.js.flow
CHANGED
|
@@ -110,6 +110,13 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
|
|
|
110
110
|
export type PreloadFn = (
|
|
111
111
|
sources: Array<FastImageSource>,
|
|
112
112
|
) => Promise<Array<PreloadResult>>
|
|
113
|
+
export type PlaceholderSource = {
|
|
114
|
+
uri?: string,
|
|
115
|
+
headers?: Object,
|
|
116
|
+
cacheKey?: string,
|
|
117
|
+
cache?: CacheControls,
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
export type SourcePlaceholder = {
|
|
114
121
|
thumbhash?: string,
|
|
115
122
|
blurDataUri?: string,
|
|
@@ -138,8 +145,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
138
145
|
onLoadStart?: ?() => void,
|
|
139
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
140
147
|
|
|
141
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
142
|
-
placeholder?: ?(number | string | SourcePlaceholder),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
|
+
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
143
150
|
// Deprecated: use placeholder, which is the same.
|
|
144
151
|
defaultSource?: ?number,
|
|
145
152
|
|
package/dist/index.web.js
CHANGED
|
@@ -241,13 +241,18 @@ function cssPosition({ x, y }) {
|
|
|
241
241
|
}
|
|
242
242
|
//#endregion
|
|
243
243
|
//#region src/placeholders.ts
|
|
244
|
+
function placeholderSource(value) {
|
|
245
|
+
const uri = typeof value === "string" ? value : value && typeof value === "object" ? value.uri : void 0;
|
|
246
|
+
if (typeof uri !== "string" || !/^https?:/i.test(uri)) return void 0;
|
|
247
|
+
return typeof value === "string" ? { uri } : value;
|
|
248
|
+
}
|
|
244
249
|
function hasSourcePlaceholder(value) {
|
|
245
250
|
return !!value && typeof value === "object" && ("thumbhash" in value || "blurDataUri" in value);
|
|
246
251
|
}
|
|
247
252
|
function dataUri(value) {
|
|
248
253
|
if (typeof value !== "string" || value === "") return void 0;
|
|
249
254
|
if (/^data:/i.test(value)) return value;
|
|
250
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("placeholder-url", `react-native-fast-image: \`
|
|
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");
|
|
251
256
|
}
|
|
252
257
|
//#endregion
|
|
253
258
|
//#region src/index.web.tsx
|
|
@@ -257,7 +262,7 @@ const notSupported = {
|
|
|
257
262
|
};
|
|
258
263
|
const useClientLayoutEffect = globalThis.document === void 0 ? useEffect : useLayoutEffect;
|
|
259
264
|
function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loading, sizes, dynamicRangeLimit, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
|
|
260
|
-
var _resolveUri;
|
|
265
|
+
var _resolveUri, _placeholderSource;
|
|
261
266
|
if (typeof __DEV__ !== "undefined" && __DEV__) warnIfChildren(children);
|
|
262
267
|
const { accessibilityLabel, "aria-label": ariaLabel, resizeMode: _resizeMode, ...viewProps } = props;
|
|
263
268
|
const label = ariaLabel || accessibilityLabel || void 0;
|
|
@@ -269,7 +274,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
269
274
|
const priority = sourcePriority(source);
|
|
270
275
|
const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
|
|
271
276
|
const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
|
|
272
|
-
const
|
|
277
|
+
const urlPlaceholder = (_placeholderSource = placeholderSource(placeholder)) === null || _placeholderSource === void 0 ? void 0 : _placeholderSource.uri;
|
|
278
|
+
const [failedPlaceholder, setFailedPlaceholder] = useState();
|
|
279
|
+
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : urlPlaceholder ? void 0 : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
273
280
|
const [result, setResult] = useState();
|
|
274
281
|
const ok = result && result.key === key ? result.ok : void 0;
|
|
275
282
|
const svgId = useId().replace(/[^\w-]/g, "");
|
|
@@ -278,7 +285,7 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
278
285
|
const fit = resolveObjectFit(objectFit, style);
|
|
279
286
|
const position = cssPosition(objectPositionOrCentered(objectPosition));
|
|
280
287
|
const filter = [blurRadius ? `blur(${blurRadius}px)` : "", tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") || void 0;
|
|
281
|
-
const showDefault =
|
|
288
|
+
const showDefault = ok !== true;
|
|
282
289
|
useClientLayoutEffect(() => {
|
|
283
290
|
if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
|
|
284
291
|
}, [key]);
|
|
@@ -299,6 +306,18 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
299
306
|
position,
|
|
300
307
|
filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
|
|
301
308
|
alt: key ? "" : label !== null && label !== void 0 ? label : ""
|
|
309
|
+
}) : null, urlPlaceholder && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
|
|
310
|
+
key: `placeholder ${urlPlaceholder}`,
|
|
311
|
+
src: urlPlaceholder,
|
|
312
|
+
loading,
|
|
313
|
+
priority: "high",
|
|
314
|
+
dynamicRangeLimit,
|
|
315
|
+
fit: fit === "scale-down" || fit === "none" ? "contain" : fit,
|
|
316
|
+
position,
|
|
317
|
+
filter: tintColor ? `url(#${tintId})` : void 0,
|
|
318
|
+
hidden: failedPlaceholder === urlPlaceholder,
|
|
319
|
+
onError: () => setFailedPlaceholder(urlPlaceholder),
|
|
320
|
+
alt: ""
|
|
302
321
|
}) : null, key && src ? /* @__PURE__ */ React.createElement(Picture, {
|
|
303
322
|
key: several ? "sizes" : key,
|
|
304
323
|
src,
|
|
@@ -370,10 +389,9 @@ function shared(source, key, preview) {
|
|
|
370
389
|
function resolveUri(source) {
|
|
371
390
|
let uri;
|
|
372
391
|
if (typeof source === "number") {
|
|
373
|
-
var _asset$scales$;
|
|
374
392
|
const asset = getAssetByID(source);
|
|
375
393
|
if (!asset) return void 0;
|
|
376
|
-
const scale =
|
|
394
|
+
const scale = assetScale(asset);
|
|
377
395
|
const suffix = scale !== 1 ? `@${scale}x` : "";
|
|
378
396
|
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`;
|
|
379
397
|
} else if (typeof source === "string") uri = source;
|
|
@@ -382,9 +400,24 @@ function resolveUri(source) {
|
|
|
382
400
|
const svg = uri === null || uri === void 0 ? void 0 : uri.match(/^(data:image\/svg\+xml;utf8,)(.*)/);
|
|
383
401
|
return svg ? svg[1] + encodeURIComponent(svg[2]) : uri || void 0;
|
|
384
402
|
}
|
|
385
|
-
function
|
|
403
|
+
function assetScale(asset) {
|
|
404
|
+
var _asset$scales$;
|
|
405
|
+
return asset.scales.reduce((best, scale) => Math.abs(scale - PixelRatio.get()) < Math.abs(best - PixelRatio.get()) ? scale : best, (_asset$scales$ = asset.scales[0]) !== null && _asset$scales$ !== void 0 ? _asset$scales$ : 1);
|
|
406
|
+
}
|
|
407
|
+
function sizedSource(source) {
|
|
408
|
+
if (typeof source !== "number") return source;
|
|
409
|
+
const asset = getAssetByID(source);
|
|
410
|
+
return asset ? {
|
|
411
|
+
uri: resolveUri(source),
|
|
412
|
+
width: asset.width,
|
|
413
|
+
height: asset.height,
|
|
414
|
+
scale: assetScale(asset)
|
|
415
|
+
} : void 0;
|
|
416
|
+
}
|
|
417
|
+
function sizedSources(entries) {
|
|
386
418
|
var _largest$uri, _sources$find;
|
|
387
|
-
const
|
|
419
|
+
const sources = entries.map(sizedSource);
|
|
420
|
+
const widths = sources.filter((source) => !!(source === null || source === void 0 ? void 0 : source.uri) && !!source.width).map((source) => {
|
|
388
421
|
var _source$width, _source$scale;
|
|
389
422
|
return {
|
|
390
423
|
uri: source.uri,
|
package/dist/index.web.js.flow
CHANGED
|
@@ -110,6 +110,13 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
|
|
|
110
110
|
export type PreloadFn = (
|
|
111
111
|
sources: Array<FastImageSource>,
|
|
112
112
|
) => Promise<Array<PreloadResult>>
|
|
113
|
+
export type PlaceholderSource = {
|
|
114
|
+
uri?: string,
|
|
115
|
+
headers?: Object,
|
|
116
|
+
cacheKey?: string,
|
|
117
|
+
cache?: CacheControls,
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
export type SourcePlaceholder = {
|
|
114
121
|
thumbhash?: string,
|
|
115
122
|
blurDataUri?: string,
|
|
@@ -138,8 +145,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
138
145
|
onLoadStart?: ?() => void,
|
|
139
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
140
147
|
|
|
141
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
142
|
-
placeholder?: ?(number | string | SourcePlaceholder),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
|
+
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
143
150
|
// Deprecated: use placeholder, which is the same.
|
|
144
151
|
defaultSource?: ?number,
|
|
145
152
|
|
package/dist/placeholders.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { SourcePlaceholder } from './index';
|
|
1
|
+
import type { PlaceholderSource, SourcePlaceholder } from './index';
|
|
2
2
|
export declare function isImageName(value: string): boolean;
|
|
3
|
+
export declare function placeholderSource(value: unknown): PlaceholderSource | undefined;
|
|
3
4
|
export declare function hasSourcePlaceholder(value: unknown): value is SourcePlaceholder;
|
|
4
5
|
export declare function dataUri(value: unknown): string | undefined;
|
|
@@ -20,9 +20,7 @@ type LoadEndEvent = Readonly<{
|
|
|
20
20
|
}>;
|
|
21
21
|
export interface NativeProps extends ViewProps {
|
|
22
22
|
source?: CodegenTypes.UnsafeMixed;
|
|
23
|
-
sources?: CodegenTypes.UnsafeMixed;
|
|
24
23
|
placeholder?: CodegenTypes.UnsafeMixed;
|
|
25
|
-
sourcePlaceholders?: ReadonlyArray<string>;
|
|
26
24
|
objectFit?: CodegenTypes.WithDefault<string, 'cover'>;
|
|
27
25
|
objectPositionX?: CodegenTypes.WithDefault<CodegenTypes.Float, 0.5>;
|
|
28
26
|
objectPositionXOffset?: CodegenTypes.Float;
|
|
@@ -15,6 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
15
15
|
// Main thread.
|
|
16
16
|
+ (nullable UIImage*) imageForPlaceholder: (nullable NSString*)placeholder;
|
|
17
17
|
|
|
18
|
+
// Whether a placeholder is a data uri (a blurDataUri) rather than a ThumbHash.
|
|
19
|
+
+ (BOOL) isDataUri: (nullable NSString*)placeholder;
|
|
20
|
+
|
|
18
21
|
// The placeholder for a base64 ThumbHash (standard or url-safe, with or
|
|
19
22
|
// without padding), or nil for an empty or invalid one. Main thread.
|
|
20
23
|
+ (nullable UIImage*) imageForThumbHash: (nullable NSString*)thumbhash;
|
|
@@ -200,8 +200,12 @@ static UIImage* FFFBlurredPreview(UIImage* image) {
|
|
|
200
200
|
return cache;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
+ (BOOL) isDataUri: (NSString*)placeholder {
|
|
204
|
+
return placeholder && [placeholder rangeOfString: @"data:" options: NSCaseInsensitiveSearch | NSAnchoredSearch].location != NSNotFound;
|
|
205
|
+
}
|
|
206
|
+
|
|
203
207
|
+ (UIImage*) imageForPlaceholder: (NSString*)placeholder {
|
|
204
|
-
if ([
|
|
208
|
+
if (![self isDataUri: placeholder]) {
|
|
205
209
|
return [self imageForThumbHash: placeholder];
|
|
206
210
|
}
|
|
207
211
|
UIImage* cached = [[self cache] objectForKey: placeholder];
|
|
@@ -28,17 +28,23 @@ typedef NS_ENUM(NSInteger, FFFObjectFit) {
|
|
|
28
28
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoad;
|
|
29
29
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadEnd;
|
|
30
30
|
@property (nonatomic, assign) FFFObjectFit objectFit;
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
// loads the one whose size is closest to its own.
|
|
31
|
+
// The `source` prop: one source, or several (sizes of one image), of which
|
|
32
|
+
// the view loads the one whose size is closest to its own.
|
|
34
33
|
@property (nonatomic, copy) NSArray<FFFastImageSource *> *sources;
|
|
35
34
|
// The `placeholder` prop.
|
|
36
35
|
@property (nonatomic, strong) UIImage *placeholder;
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
36
|
+
// `placeholder` as a url (a source): loaded apart from the
|
|
37
|
+
// image, at a high priority (or only from the cache, with cache cacheOnly),
|
|
38
|
+
// it shows over the other placeholders once it has.
|
|
39
|
+
@property (nonatomic, strong) FFFastImageSource *placeholderSource;
|
|
40
|
+
// The sources' ThumbHashes or blurDataUris (data uris), by the source's index
|
|
41
|
+
// (an empty string for none): the picked source's shows instead of
|
|
42
|
+
// placeholder. Set apart from the sources, so a new one for the same image
|
|
43
|
+
// doesn't load it again.
|
|
41
44
|
@property (nonatomic, copy) NSArray<NSString *> *sourcePlaceholders;
|
|
45
|
+
// `placeholder` as a thumbhash or blurDataUri: shows for the sources without
|
|
46
|
+
// their own.
|
|
47
|
+
@property (nonatomic, copy) NSString *placeholderHash;
|
|
42
48
|
@property (nonatomic, strong) UIColor *imageColor;
|
|
43
49
|
// When it changes, the next image doesn't replace the current one: the view
|
|
44
50
|
// clears first (for views reused for other content, like list rows).
|