react-native-fast-image 9.2.1 → 9.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 +40 -9
- package/android/src/main/java/com/dylanvann/fastimage/FastImageBlur.java +18 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +5 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +22 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +27 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvgRenderer.java +10 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +5 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +157 -14
- package/dist/index.cjs.js +50 -18
- package/dist/index.cjs.js.flow +3 -0
- package/dist/index.cjs.web.js +241 -130
- package/dist/index.cjs.web.js.flow +3 -0
- package/dist/index.d.ts +38 -6
- package/dist/index.js +50 -18
- package/dist/index.js.flow +3 -0
- package/dist/index.web.js +243 -132
- package/dist/index.web.js.flow +3 -0
- package/dist/objectFit.d.ts +3 -0
- package/ios/FastImage/FFFDownsampledImage.h +8 -0
- package/ios/FastImage/FFFDownsampledImage.m +16 -0
- package/ios/FastImage/FFFastImageView.h +7 -1
- package/ios/FastImage/FFFastImageView.m +15 -5
- package/ios/FastImage/FFFastImageViewManager.m +3 -1
- package/ios/FastImage/RCTConvert+FFFastImage.h +3 -0
- package/ios/FastImage/RCTConvert+FFFastImage.m +11 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,30 +19,61 @@ const cacheControl = {
|
|
|
19
19
|
cacheOnly: "cacheOnly"
|
|
20
20
|
};
|
|
21
21
|
//#endregion
|
|
22
|
-
//#region src/
|
|
23
|
-
|
|
24
|
-
if (!defaultSource) return null;
|
|
25
|
-
if (Platform.OS === "android") {
|
|
26
|
-
const resolved = Image.resolveAssetSource(defaultSource);
|
|
27
|
-
if (resolved) return resolved.uri;
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
return defaultSource;
|
|
31
|
-
};
|
|
32
|
-
function tintColorFromStyle(style) {
|
|
22
|
+
//#region src/objectFit.ts
|
|
23
|
+
function fromStyle(style, key) {
|
|
33
24
|
if (Array.isArray(style)) {
|
|
34
25
|
for (let i = style.length - 1; i >= 0; i--) {
|
|
35
|
-
const found =
|
|
26
|
+
const found = fromStyle(style[i], key);
|
|
36
27
|
if (found !== void 0) return found;
|
|
37
28
|
}
|
|
38
29
|
return;
|
|
39
30
|
}
|
|
40
31
|
if (typeof style === "number") {
|
|
41
32
|
const flattened = StyleSheet.flatten(style);
|
|
42
|
-
return flattened ? flattened
|
|
33
|
+
return flattened ? flattened[key] : void 0;
|
|
43
34
|
}
|
|
44
|
-
return style && typeof style === "object" ? style
|
|
35
|
+
return style && typeof style === "object" ? style[key] : void 0;
|
|
36
|
+
}
|
|
37
|
+
const RESIZE_MODE_FIT = {
|
|
38
|
+
contain: "contain",
|
|
39
|
+
cover: "cover",
|
|
40
|
+
stretch: "fill",
|
|
41
|
+
center: "center",
|
|
42
|
+
repeat: "repeat"
|
|
43
|
+
};
|
|
44
|
+
const FITS = {
|
|
45
|
+
fill: true,
|
|
46
|
+
contain: true,
|
|
47
|
+
cover: true,
|
|
48
|
+
none: true,
|
|
49
|
+
"scale-down": true
|
|
50
|
+
};
|
|
51
|
+
function resolveObjectFit(objectFit, style, resizeMode) {
|
|
52
|
+
var _RESIZE_MODE_FIT$resi;
|
|
53
|
+
const fit = objectFit != null ? objectFit : fromStyle(style, "objectFit");
|
|
54
|
+
if (fit && FITS[fit] === true) return fit;
|
|
55
|
+
return (_RESIZE_MODE_FIT$resi = RESIZE_MODE_FIT[resizeMode]) !== null && _RESIZE_MODE_FIT$resi !== void 0 ? _RESIZE_MODE_FIT$resi : "cover";
|
|
45
56
|
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/index.tsx
|
|
59
|
+
const resolveDefaultSource = (defaultSource) => {
|
|
60
|
+
if (!defaultSource) return null;
|
|
61
|
+
if (Platform.OS === "android") {
|
|
62
|
+
const resolved = Image.resolveAssetSource(defaultSource);
|
|
63
|
+
if (resolved) return resolved.uri;
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return defaultSource;
|
|
67
|
+
};
|
|
68
|
+
const NATIVE_RESIZE_MODE = {
|
|
69
|
+
fill: "stretch",
|
|
70
|
+
contain: "contain",
|
|
71
|
+
cover: "cover",
|
|
72
|
+
none: "none",
|
|
73
|
+
"scale-down": "scale-down",
|
|
74
|
+
center: "center",
|
|
75
|
+
repeat: "repeat"
|
|
76
|
+
};
|
|
46
77
|
function loopCount(loop) {
|
|
47
78
|
if (loop === void 0) return -1;
|
|
48
79
|
if (loop === true) return 0;
|
|
@@ -84,7 +115,7 @@ function withoutCache(source) {
|
|
|
84
115
|
const { cache: _cache, ...rest } = source || {};
|
|
85
116
|
return rest;
|
|
86
117
|
}
|
|
87
|
-
function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, transition, forwardedRef, onLayout, pointerEvents, hitSlop, onStartShouldSetResponder, onStartShouldSetResponderCapture, onMoveShouldSetResponder, onMoveShouldSetResponderCapture, onResponderGrant, onResponderReject, onResponderStart, onResponderMove, onResponderEnd, onResponderRelease, onResponderTerminationRequest, onResponderTerminate, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, onTouchEndCapture, ...viewProps }) {
|
|
118
|
+
function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, objectFit, resizeMode = "cover", loop, transition, forwardedRef, onLayout, pointerEvents, hitSlop, onStartShouldSetResponder, onStartShouldSetResponderCapture, onMoveShouldSetResponder, onMoveShouldSetResponderCapture, onResponderGrant, onResponderReject, onResponderStart, onResponderMove, onResponderEnd, onResponderRelease, onResponderTerminationRequest, onResponderTerminate, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, onTouchEndCapture, ...viewProps }) {
|
|
88
119
|
const { onClick, ...props } = viewProps;
|
|
89
120
|
const fallbackResult = useRef(void 0);
|
|
90
121
|
const wrapperProps = {
|
|
@@ -114,7 +145,8 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
114
145
|
...props,
|
|
115
146
|
pointerEvents: pointerEvents === "box-none" ? "none" : void 0
|
|
116
147
|
};
|
|
117
|
-
const resolvedTintColor = tintColor != null ? tintColor :
|
|
148
|
+
const resolvedTintColor = tintColor != null ? tintColor : fromStyle(style, "tintColor");
|
|
149
|
+
const mode = NATIVE_RESIZE_MODE[resolveObjectFit(objectFit, style, resizeMode)];
|
|
118
150
|
if (fallback) {
|
|
119
151
|
const cleanedSource = typeof source === "number" ? source : Array.isArray(source) ? source.map(withoutCache) : withoutCache(source);
|
|
120
152
|
const resolvedSource = Image.resolveAssetSource(cleanedSource);
|
|
@@ -152,7 +184,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
152
184
|
error: "Failed to load the image"
|
|
153
185
|
});
|
|
154
186
|
}),
|
|
155
|
-
resizeMode
|
|
187
|
+
resizeMode: mode === "none" || mode === "scale-down" ? "center" : mode
|
|
156
188
|
}), children);
|
|
157
189
|
}
|
|
158
190
|
const sources = Array.isArray(source) ? source.length === 1 ? void 0 : source : void 0;
|
|
@@ -178,7 +210,7 @@ function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgre
|
|
|
178
210
|
onFastImageLoad: onLoad,
|
|
179
211
|
onFastImageError: onError,
|
|
180
212
|
onFastImageLoadEnd: onLoadEnd && ((event) => onLoadEnd(loadResult(event.nativeEvent))),
|
|
181
|
-
resizeMode
|
|
213
|
+
resizeMode: mode
|
|
182
214
|
}), children);
|
|
183
215
|
}
|
|
184
216
|
const FastImageMemo = memo(FastImageBase);
|
package/dist/index.js.flow
CHANGED
|
@@ -49,6 +49,7 @@ export type CacheControl = $ReadOnly<{|
|
|
|
49
49
|
|}>
|
|
50
50
|
|
|
51
51
|
export type ResizeModes = $Values<ResizeMode>
|
|
52
|
+
export type ObjectFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
|
|
52
53
|
export type Priorities = $Values<Priority>
|
|
53
54
|
export type CacheControls = $Values<CacheControl>
|
|
54
55
|
|
|
@@ -123,6 +124,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
123
124
|
defaultSource?: ?number,
|
|
124
125
|
|
|
125
126
|
tintColor?: number | string,
|
|
127
|
+
objectFit?: ?ObjectFit,
|
|
128
|
+
// Deprecated: objectFit, except for 'repeat'.
|
|
126
129
|
resizeMode?: ?ResizeModes,
|
|
127
130
|
fallback?: ?boolean,
|
|
128
131
|
recyclingKey?: ?string,
|
package/dist/index.web.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { forwardRef, memo, useEffect, useRef } from "react";
|
|
2
|
-
import { Image, StyleSheet, View } from "react-native";
|
|
1
|
+
import React, { forwardRef, memo, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import { Image, PixelRatio, StyleSheet, View } from "react-native";
|
|
3
|
+
import { getAssetByID } from "react-native-web/dist/modules/AssetRegistry";
|
|
3
4
|
//#region src/constants.ts
|
|
4
5
|
const resizeMode = {
|
|
5
6
|
contain: "contain",
|
|
@@ -19,142 +20,279 @@ const cacheControl = {
|
|
|
19
20
|
cacheOnly: "cacheOnly"
|
|
20
21
|
};
|
|
21
22
|
//#endregion
|
|
23
|
+
//#region src/objectFit.ts
|
|
24
|
+
function fromStyle(style, key) {
|
|
25
|
+
if (Array.isArray(style)) {
|
|
26
|
+
for (let i = style.length - 1; i >= 0; i--) {
|
|
27
|
+
const found = fromStyle(style[i], key);
|
|
28
|
+
if (found !== void 0) return found;
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (typeof style === "number") {
|
|
33
|
+
const flattened = StyleSheet.flatten(style);
|
|
34
|
+
return flattened ? flattened[key] : void 0;
|
|
35
|
+
}
|
|
36
|
+
return style && typeof style === "object" ? style[key] : void 0;
|
|
37
|
+
}
|
|
38
|
+
const RESIZE_MODE_FIT = {
|
|
39
|
+
contain: "contain",
|
|
40
|
+
cover: "cover",
|
|
41
|
+
stretch: "fill",
|
|
42
|
+
center: "center",
|
|
43
|
+
repeat: "repeat"
|
|
44
|
+
};
|
|
45
|
+
const FITS = {
|
|
46
|
+
fill: true,
|
|
47
|
+
contain: true,
|
|
48
|
+
cover: true,
|
|
49
|
+
none: true,
|
|
50
|
+
"scale-down": true
|
|
51
|
+
};
|
|
52
|
+
function resolveObjectFit(objectFit, style, resizeMode) {
|
|
53
|
+
var _RESIZE_MODE_FIT$resi;
|
|
54
|
+
const fit = objectFit != null ? objectFit : fromStyle(style, "objectFit");
|
|
55
|
+
if (fit && FITS[fit] === true) return fit;
|
|
56
|
+
return (_RESIZE_MODE_FIT$resi = RESIZE_MODE_FIT[resizeMode]) !== null && _RESIZE_MODE_FIT$resi !== void 0 ? _RESIZE_MODE_FIT$resi : "cover";
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
22
59
|
//#region src/index.web.tsx
|
|
60
|
+
var _useId;
|
|
23
61
|
const notSupported = {
|
|
24
62
|
ok: false,
|
|
25
63
|
error: "Not supported on the web"
|
|
26
64
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
65
|
+
let ids = 0;
|
|
66
|
+
const useCounterId = () => useState(() => `${++ids}`)[0];
|
|
67
|
+
const useSvgId = (_useId = React.useId) !== null && _useId !== void 0 ? _useId : useCounterId;
|
|
68
|
+
const useClientLayoutEffect = globalThis.document === void 0 ? useEffect : useLayoutEffect;
|
|
69
|
+
function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, resizeMode: mode = "cover", forwardedRef, onLayout, pointerEvents, fallback: _fallback, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
|
|
70
|
+
const { accessibilityLabel, "aria-label": ariaLabel, ...viewProps } = props;
|
|
71
|
+
const label = ariaLabel || accessibilityLabel || void 0;
|
|
72
|
+
const several = Array.isArray(source) && source.length > 1 ? source : void 0;
|
|
73
|
+
const sized = several ? sizedSources(several) : void 0;
|
|
74
|
+
const src = sized ? sized.src : resolveUri(source);
|
|
75
|
+
const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
|
|
76
|
+
const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
|
|
77
|
+
const defaultUri = resolveUri(defaultSource);
|
|
78
|
+
const [result, setResult] = useState();
|
|
79
|
+
const ok = result && result.key === key ? result.ok : void 0;
|
|
80
|
+
const tintId = `fast-image-tint-${useSvgId().replace(/[^\w-]/g, "")}`;
|
|
81
|
+
const resolved = resolveObjectFit(objectFit, style, mode);
|
|
82
|
+
const fit = resolved === "center" ? "scale-down" : several && resolved === "repeat" ? "cover" : resolved;
|
|
83
|
+
const filter = [blurRadius ? `blur(${blurRadius}px)` : "", tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") || void 0;
|
|
84
|
+
const showDefault = !!defaultUri && ok !== true;
|
|
85
|
+
useClientLayoutEffect(() => {
|
|
86
|
+
if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
|
|
87
|
+
}, [key]);
|
|
35
88
|
return /* @__PURE__ */ React.createElement(View, {
|
|
89
|
+
...viewProps,
|
|
90
|
+
"aria-label": label,
|
|
36
91
|
style: [styles.container, style],
|
|
37
92
|
onLayout,
|
|
38
93
|
pointerEvents,
|
|
39
94
|
ref: forwardedRef
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
70
|
-
width,
|
|
71
|
-
height
|
|
72
|
-
} });
|
|
95
|
+
}, defaultUri && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
|
|
96
|
+
key: `default ${defaultUri}`,
|
|
97
|
+
src: defaultUri,
|
|
98
|
+
fit,
|
|
99
|
+
filter,
|
|
100
|
+
alt: key ? "" : label !== null && label !== void 0 ? label : ""
|
|
101
|
+
}) : null, key && src ? /* @__PURE__ */ React.createElement(Picture, {
|
|
102
|
+
key: several ? "sizes" : key,
|
|
103
|
+
src,
|
|
104
|
+
srcSet,
|
|
105
|
+
lazy: !!several,
|
|
106
|
+
fit,
|
|
107
|
+
filter,
|
|
108
|
+
hidden: ok === false,
|
|
109
|
+
alt: label !== null && label !== void 0 ? label : "",
|
|
110
|
+
onLoad: (image) => {
|
|
111
|
+
const { width, height } = loadedSize(image, sized);
|
|
112
|
+
setResult({
|
|
113
|
+
key,
|
|
114
|
+
ok: true
|
|
115
|
+
});
|
|
116
|
+
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
117
|
+
width,
|
|
118
|
+
height
|
|
119
|
+
} });
|
|
120
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
121
|
+
ok: true,
|
|
122
|
+
width,
|
|
123
|
+
height
|
|
73
124
|
});
|
|
74
125
|
},
|
|
75
|
-
onError: (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
126
|
+
onError: (uri) => {
|
|
127
|
+
const error = `Failed to load resource ${uri}`;
|
|
128
|
+
setResult({
|
|
129
|
+
key,
|
|
130
|
+
ok: false
|
|
131
|
+
});
|
|
132
|
+
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
133
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
79
134
|
ok: false,
|
|
80
135
|
error
|
|
81
|
-
};
|
|
82
|
-
sent.current = Promise.resolve();
|
|
83
|
-
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
84
|
-
},
|
|
85
|
-
onLoadEnd: () => {
|
|
86
|
-
sent.current.then(() => {
|
|
87
|
-
var _result$current;
|
|
88
|
-
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd((_result$current = result.current) !== null && _result$current !== void 0 ? _result$current : {
|
|
89
|
-
ok: false,
|
|
90
|
-
error: "Failed to load the image"
|
|
91
|
-
});
|
|
92
|
-
result.current = void 0;
|
|
93
136
|
});
|
|
94
137
|
}
|
|
95
|
-
}),
|
|
138
|
+
}) : null, tintColor ? /* @__PURE__ */ React.createElement(TintFilter, {
|
|
139
|
+
id: tintId,
|
|
140
|
+
color: tintColor
|
|
141
|
+
}) : null, children);
|
|
142
|
+
}
|
|
143
|
+
function resolveUri(source) {
|
|
144
|
+
let uri;
|
|
145
|
+
if (typeof source === "number") {
|
|
146
|
+
var _asset$scales$;
|
|
147
|
+
const asset = getAssetByID(source);
|
|
148
|
+
if (!asset) return void 0;
|
|
149
|
+
const scale = 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);
|
|
150
|
+
const suffix = scale !== 1 ? `@${scale}x` : "";
|
|
151
|
+
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`;
|
|
152
|
+
} else if (typeof source === "string") uri = source;
|
|
153
|
+
else if (Array.isArray(source)) return resolveUri(source[0]);
|
|
154
|
+
else if (source && typeof source.uri === "string") uri = source.uri;
|
|
155
|
+
const svg = uri === null || uri === void 0 ? void 0 : uri.match(/^(data:image\/svg\+xml;utf8,)(.*)/);
|
|
156
|
+
return svg ? svg[1] + encodeURIComponent(svg[2]) : uri || void 0;
|
|
96
157
|
}
|
|
97
|
-
function
|
|
158
|
+
function sizedSources(sources) {
|
|
98
159
|
var _largest$uri, _sources$find;
|
|
99
|
-
const
|
|
160
|
+
const widths = sources.filter((source) => (source === null || source === void 0 ? void 0 : source.uri) && source.width).map((source) => {
|
|
100
161
|
var _source$width, _source$scale;
|
|
101
162
|
return {
|
|
102
163
|
uri: source.uri,
|
|
103
164
|
width: Math.round(((_source$width = source.width) !== null && _source$width !== void 0 ? _source$width : 0) * ((_source$scale = source.scale) !== null && _source$scale !== void 0 ? _source$scale : 1))
|
|
104
165
|
};
|
|
105
166
|
});
|
|
106
|
-
const largest =
|
|
107
|
-
|
|
108
|
-
|
|
167
|
+
const largest = widths.reduce((best, source) => !best || source.width > best.width ? source : best, void 0);
|
|
168
|
+
return {
|
|
169
|
+
src: (_largest$uri = largest === null || largest === void 0 ? void 0 : largest.uri) !== null && _largest$uri !== void 0 ? _largest$uri : (_sources$find = sources.find((source) => source === null || source === void 0 ? void 0 : source.uri)) === null || _sources$find === void 0 ? void 0 : _sources$find.uri,
|
|
170
|
+
srcSet: widths.length > 0 ? widths.map((source) => `${source.uri} ${source.width}w`).join(", ") : void 0,
|
|
171
|
+
widths
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function loadedSize(image, sized) {
|
|
175
|
+
var _picked$width;
|
|
176
|
+
const picked = (sized === null || sized === void 0 ? void 0 : sized.srcSet) ? sized.widths.find((source) => new URL(source.uri, image.currentSrc).href === image.currentSrc) : void 0;
|
|
177
|
+
return {
|
|
178
|
+
width: (_picked$width = picked === null || picked === void 0 ? void 0 : picked.width) !== null && _picked$width !== void 0 ? _picked$width : image.naturalWidth,
|
|
179
|
+
height: picked ? Math.round(image.naturalHeight * picked.width / image.naturalWidth) : image.naturalHeight
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const fill = {
|
|
183
|
+
position: "absolute",
|
|
184
|
+
top: 0,
|
|
185
|
+
left: 0,
|
|
186
|
+
width: "100%",
|
|
187
|
+
height: "100%"
|
|
188
|
+
};
|
|
189
|
+
function Picture({ src, srcSet, lazy, fit, filter, hidden, alt, onLoad, onError }) {
|
|
190
|
+
const repeat = fit === "repeat";
|
|
191
|
+
const image = useRef(null);
|
|
192
|
+
const tiles = useRef(null);
|
|
193
|
+
const mounted = useRef(true);
|
|
194
|
+
const handled = useRef(void 0);
|
|
195
|
+
const [natural, setNatural] = useState();
|
|
196
|
+
const [box, setBox] = useState();
|
|
197
|
+
const loaded = (target) => {
|
|
198
|
+
var _handled$current, _target$decode;
|
|
199
|
+
const file = target.currentSrc || src;
|
|
200
|
+
const sources = srcSet !== null && srcSet !== void 0 ? srcSet : src;
|
|
201
|
+
if (!mounted.current || ((_handled$current = handled.current) === null || _handled$current === void 0 ? void 0 : _handled$current.sources) === sources && handled.current.file === file) return;
|
|
202
|
+
handled.current = {
|
|
203
|
+
sources,
|
|
204
|
+
file
|
|
205
|
+
};
|
|
206
|
+
setNatural({
|
|
207
|
+
width: target.naturalWidth,
|
|
208
|
+
height: target.naturalHeight
|
|
209
|
+
});
|
|
210
|
+
const done = () => {
|
|
211
|
+
if (mounted.current) onLoad === null || onLoad === void 0 || onLoad(target);
|
|
212
|
+
};
|
|
213
|
+
Promise.resolve((_target$decode = target.decode) === null || _target$decode === void 0 ? void 0 : _target$decode.call(target)).then(done, done);
|
|
214
|
+
};
|
|
109
215
|
useEffect(() => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
216
|
+
mounted.current = true;
|
|
217
|
+
return () => {
|
|
218
|
+
mounted.current = false;
|
|
219
|
+
};
|
|
220
|
+
}, []);
|
|
221
|
+
useClientLayoutEffect(() => {
|
|
222
|
+
var _target$decode2;
|
|
223
|
+
const target = image.current;
|
|
224
|
+
if (!(target === null || target === void 0 ? void 0 : target.complete)) return;
|
|
225
|
+
if (target.naturalWidth > 0) return loaded(target);
|
|
226
|
+
Promise.resolve((_target$decode2 = target.decode) === null || _target$decode2 === void 0 ? void 0 : _target$decode2.call(target)).then(() => loaded(target), () => {
|
|
227
|
+
if (mounted.current) onError === null || onError === void 0 || onError(target.currentSrc || src);
|
|
228
|
+
});
|
|
229
|
+
}, []);
|
|
230
|
+
useClientLayoutEffect(() => {
|
|
231
|
+
const Observer = globalThis.ResizeObserver;
|
|
232
|
+
const node = tiles.current;
|
|
233
|
+
if (!repeat || !node || !Observer) return;
|
|
234
|
+
const observer = new Observer((entries) => {
|
|
235
|
+
const { width, height } = entries[0].contentRect;
|
|
236
|
+
setBox({
|
|
237
|
+
width,
|
|
238
|
+
height
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
observer.observe(node);
|
|
242
|
+
return () => observer.disconnect();
|
|
243
|
+
}, [repeat]);
|
|
244
|
+
const img = /* @__PURE__ */ React.createElement("img", {
|
|
245
|
+
ref: image,
|
|
113
246
|
src,
|
|
114
247
|
srcSet,
|
|
115
248
|
sizes: srcSet ? "auto, 100vw" : void 0,
|
|
116
|
-
loading: "lazy",
|
|
117
|
-
alt
|
|
249
|
+
loading: lazy ? "lazy" : void 0,
|
|
250
|
+
alt,
|
|
118
251
|
draggable: false,
|
|
119
252
|
style: {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
filter: blurRadius ? `blur(${blurRadius}px)` : void 0
|
|
253
|
+
...fill,
|
|
254
|
+
objectFit: repeat ? "cover" : fit,
|
|
255
|
+
filter: repeat ? void 0 : filter,
|
|
256
|
+
opacity: repeat || hidden ? 0 : void 0
|
|
125
257
|
},
|
|
126
|
-
onLoad: (event) =>
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
const loadedWidth = (_picked$width = picked === null || picked === void 0 ? void 0 : picked.width) !== null && _picked$width !== void 0 ? _picked$width : image.naturalWidth;
|
|
131
|
-
const loadedHeight = picked ? Math.round(image.naturalHeight * picked.width / image.naturalWidth) : image.naturalHeight;
|
|
132
|
-
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
133
|
-
width: loadedWidth,
|
|
134
|
-
height: loadedHeight
|
|
135
|
-
} });
|
|
136
|
-
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
137
|
-
ok: true,
|
|
138
|
-
width: loadedWidth,
|
|
139
|
-
height: loadedHeight
|
|
140
|
-
});
|
|
141
|
-
},
|
|
142
|
-
onError: () => {
|
|
143
|
-
const error = "Failed to load the image";
|
|
144
|
-
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
145
|
-
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
146
|
-
ok: false,
|
|
147
|
-
error
|
|
148
|
-
});
|
|
258
|
+
onLoad: (event) => loaded(event.currentTarget),
|
|
259
|
+
onError: (event) => {
|
|
260
|
+
const target = event.currentTarget;
|
|
261
|
+
if (mounted.current) onError === null || onError === void 0 || onError((target === null || target === void 0 ? void 0 : target.currentSrc) || src);
|
|
149
262
|
}
|
|
150
263
|
});
|
|
264
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, repeat ? /* @__PURE__ */ React.createElement("div", {
|
|
265
|
+
ref: tiles,
|
|
266
|
+
style: {
|
|
267
|
+
...fill,
|
|
268
|
+
backgroundImage: `url("${src}")`,
|
|
269
|
+
backgroundRepeat: "repeat",
|
|
270
|
+
backgroundPosition: "0",
|
|
271
|
+
backgroundSize: tileSize(natural, box),
|
|
272
|
+
filter,
|
|
273
|
+
opacity: hidden ? 0 : void 0
|
|
274
|
+
}
|
|
275
|
+
}) : null, img);
|
|
151
276
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
277
|
+
function tileSize(natural, box) {
|
|
278
|
+
if (!(natural === null || natural === void 0 ? void 0 : natural.width) || !natural.height || !(box === null || box === void 0 ? void 0 : box.width) || !box.height) return;
|
|
279
|
+
const scale = Math.min(1, box.width / natural.width, box.height / natural.height);
|
|
280
|
+
return `${Math.ceil(scale * natural.width)}px ${Math.ceil(scale * natural.height)}px`;
|
|
281
|
+
}
|
|
282
|
+
function TintFilter({ id, color }) {
|
|
283
|
+
return /* @__PURE__ */ React.createElement("svg", { style: tintStyle }, /* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("filter", { id }, /* @__PURE__ */ React.createElement("feFlood", {
|
|
284
|
+
floodColor: String(color),
|
|
285
|
+
key: String(color)
|
|
286
|
+
}), /* @__PURE__ */ React.createElement("feComposite", {
|
|
287
|
+
in2: "SourceAlpha",
|
|
288
|
+
operator: "in"
|
|
289
|
+
}))));
|
|
290
|
+
}
|
|
291
|
+
const tintStyle = {
|
|
292
|
+
position: "absolute",
|
|
293
|
+
width: 0,
|
|
294
|
+
height: 0,
|
|
295
|
+
visibility: "hidden"
|
|
158
296
|
};
|
|
159
297
|
const FastImageMemo = memo(FastImageBase);
|
|
160
298
|
const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.createElement(FastImageMemo, {
|
|
@@ -162,24 +300,6 @@ const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.crea
|
|
|
162
300
|
...props
|
|
163
301
|
}));
|
|
164
302
|
FastImageComponent.displayName = "FastImage";
|
|
165
|
-
async function loadedSize(loaded, view, uri) {
|
|
166
|
-
var _view$querySelector;
|
|
167
|
-
if (loaded === null || loaded === void 0 ? void 0 : loaded.naturalWidth) return {
|
|
168
|
-
width: loaded.naturalWidth,
|
|
169
|
-
height: loaded.naturalHeight
|
|
170
|
-
};
|
|
171
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
172
|
-
const shown = view === null || view === void 0 || (_view$querySelector = view.querySelector) === null || _view$querySelector === void 0 ? void 0 : _view$querySelector.call(view, "img");
|
|
173
|
-
if (shown === null || shown === void 0 ? void 0 : shown.naturalWidth) return {
|
|
174
|
-
width: shown.naturalWidth,
|
|
175
|
-
height: shown.naturalHeight
|
|
176
|
-
};
|
|
177
|
-
const src = uri !== null && uri !== void 0 ? uri : shown === null || shown === void 0 ? void 0 : shown.src;
|
|
178
|
-
return src ? getSize(src) : {
|
|
179
|
-
width: 0,
|
|
180
|
-
height: 0
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
303
|
function getSize(uri) {
|
|
184
304
|
return new Promise((resolve) => {
|
|
185
305
|
Image.getSize(uri, (width, height) => resolve({
|
|
@@ -217,16 +337,7 @@ FastImage.clearDiskCache = () => Promise.resolve(notSupported);
|
|
|
217
337
|
FastImage.configureCache = () => Promise.resolve({});
|
|
218
338
|
FastImage.writeToCache = () => Promise.resolve(notSupported);
|
|
219
339
|
FastImage.getCachePath = () => Promise.resolve(notSupported);
|
|
220
|
-
const styles = StyleSheet.create({
|
|
221
|
-
container: { overflow: "hidden" },
|
|
222
|
-
image: {
|
|
223
|
-
position: "absolute",
|
|
224
|
-
top: 0,
|
|
225
|
-
left: 0,
|
|
226
|
-
width: "100%",
|
|
227
|
-
height: "100%"
|
|
228
|
-
}
|
|
229
|
-
});
|
|
340
|
+
const styles = StyleSheet.create({ container: { overflow: "hidden" } });
|
|
230
341
|
const FastImageWithRef = FastImageComponent;
|
|
231
342
|
const FastImageBackground = forwardRef(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ React.createElement(View, {
|
|
232
343
|
style,
|
package/dist/index.web.js.flow
CHANGED
|
@@ -49,6 +49,7 @@ export type CacheControl = $ReadOnly<{|
|
|
|
49
49
|
|}>
|
|
50
50
|
|
|
51
51
|
export type ResizeModes = $Values<ResizeMode>
|
|
52
|
+
export type ObjectFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
|
|
52
53
|
export type Priorities = $Values<Priority>
|
|
53
54
|
export type CacheControls = $Values<CacheControl>
|
|
54
55
|
|
|
@@ -123,6 +124,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
123
124
|
defaultSource?: ?number,
|
|
124
125
|
|
|
125
126
|
tintColor?: number | string,
|
|
127
|
+
objectFit?: ?ObjectFit,
|
|
128
|
+
// Deprecated: objectFit, except for 'repeat'.
|
|
126
129
|
resizeMode?: ?ResizeModes,
|
|
127
130
|
fallback?: ?boolean,
|
|
128
131
|
recyclingKey?: ?string,
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ImageStyle, ObjectFit, ResizeMode } from './index';
|
|
2
|
+
export declare function fromStyle<K extends 'tintColor' | 'objectFit'>(style: unknown, key: K): ImageStyle[K];
|
|
3
|
+
export declare function resolveObjectFit(objectFit: ObjectFit | null | undefined, style: unknown, resizeMode: ResizeMode): ObjectFit | 'repeat' | 'center';
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
// loaded this way was decoded from, for onLoad, or zero for other images.
|
|
26
26
|
+ (CGSize) sourceSizeOfImage: (UIImage*)image;
|
|
27
27
|
|
|
28
|
+
// SDWebImage puts an image it decoded smaller from the disk cache, for a view
|
|
29
|
+
// that downsamples, into the memory cache under the full-size image's key too
|
|
30
|
+
// (SDWebImageManager writes what its original cache query finds back there),
|
|
31
|
+
// where a full-size load (a view that doesn't downsample, or a preload) would
|
|
32
|
+
// find it: a blurry image, or a preload's size. Drops it there, so that load
|
|
33
|
+
// decodes the file from the disk cache. Call it before a full-size load.
|
|
34
|
+
+ (void) forgetSmallerFullSizeImageForURL: (NSURL*)url context: (SDWebImageContext*)context;
|
|
35
|
+
|
|
28
36
|
// Whether this version of SDWebImage can decode through this class: it
|
|
29
37
|
// decodes static images through the animated image class since 5.19.
|
|
30
38
|
+ (BOOL) isSupported;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
#import <SDWebImage/UIImage+Metadata.h>
|
|
6
6
|
#import <SDWebImage/NSData+ImageContentType.h>
|
|
7
7
|
#import <SDWebImage/SDImageCodersManager.h>
|
|
8
|
+
#import <SDWebImage/SDImageCache.h>
|
|
9
|
+
#import <SDWebImage/SDWebImageManager.h>
|
|
8
10
|
|
|
9
11
|
// The decode option that carries the full image's size to the view.
|
|
10
12
|
static SDImageCoderOption const FFFDecodeSourceSize = @"FFFDecodeSourceSize";
|
|
@@ -109,6 +111,20 @@ static CGSize FFFPixelSize(NSData* data) {
|
|
|
109
111
|
return [sourceSize isKindOfClass: [FFFSourceSize class]] ? ((FFFSourceSize*) sourceSize).size : CGSizeZero;
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
+ (void) forgetSmallerFullSizeImageForURL: (NSURL*)url context: (SDWebImageContext*)context {
|
|
115
|
+
SDWebImageManager* manager = [SDWebImageManager sharedManager];
|
|
116
|
+
if (!url || ![manager.imageCache isKindOfClass: [SDImageCache class]] || ![manager respondsToSelector: @selector(cacheKeyForURL:context:)]) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
SDImageCache* cache = (SDImageCache*) manager.imageCache;
|
|
120
|
+
NSString* key = [manager cacheKeyForURL: url context: context];
|
|
121
|
+
UIImage* image = [cache imageFromMemoryCacheForKey: key];
|
|
122
|
+
CGSize fullSize = [self sourceSizeOfImage: image];
|
|
123
|
+
if (image && fullSize.width > 0 && fullSize.height > 0 && !CGSizeEqualToSize(image.size, fullSize)) {
|
|
124
|
+
[cache removeImageFromMemoryForKey: key];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
112
128
|
- (instancetype) initWithData: (NSData*)data scale: (CGFloat)scale options: (SDImageCoderOptions*)options {
|
|
113
129
|
// An SVG isn't decoded smaller: it's a vector image, drawn at any size.
|
|
114
130
|
// This class can't decode it, and SDWebImage would then give it to the
|
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
#import "FFFastImageSource.h"
|
|
10
10
|
|
|
11
|
+
// resizeMode: React Native's, and `none` (objectFit="none": the image at its
|
|
12
|
+
// own size, centered, not scaled), which React Native's own resize modes only
|
|
13
|
+
// have from 0.77, with another meaning (top-left).
|
|
14
|
+
typedef RCTResizeMode FFFResizeMode;
|
|
15
|
+
static const FFFResizeMode FFFResizeModeNone = (FFFResizeMode) -2;
|
|
16
|
+
|
|
11
17
|
@interface FFFastImageView : SDAnimatedImageView
|
|
12
18
|
|
|
13
19
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadStart;
|
|
@@ -17,7 +23,7 @@
|
|
|
17
23
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageError;
|
|
18
24
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoad;
|
|
19
25
|
@property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadEnd;
|
|
20
|
-
@property (nonatomic, assign)
|
|
26
|
+
@property (nonatomic, assign) FFFResizeMode resizeMode;
|
|
21
27
|
@property (nonatomic, strong) FFFastImageSource *source;
|
|
22
28
|
// Several sources (2 or more) of the same image at different sizes: the view
|
|
23
29
|
// loads the one whose size is closest to its own.
|