react-native-fast-image 9.2.2 → 9.3.1
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 +36 -7
- 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/FastImageSharedDownloads.java +64 -19
- 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 +36 -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 +10 -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.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: use objectFit.
|
|
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.
|
|
@@ -224,7 +224,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
- (void) setResizeMode: (
|
|
227
|
+
- (void) setResizeMode: (FFFResizeMode)resizeMode {
|
|
228
228
|
if (_resizeMode != resizeMode) {
|
|
229
229
|
BOOL repeated = _resizeMode == RCTResizeModeRepeat;
|
|
230
230
|
_resizeMode = resizeMode;
|
|
@@ -242,10 +242,13 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
242
242
|
// view down to fit, as React Native's Image and Android do (#866); only a
|
|
243
243
|
// smaller one is shown at its own size. UIViewContentModeCenter alone showed
|
|
244
244
|
// large images at full size, cropped. So it depends on the image and the
|
|
245
|
-
// view's size.
|
|
245
|
+
// view's size. `none` is the image at its own size, centered, whatever its
|
|
246
|
+
// size (cropped by the view).
|
|
246
247
|
- (void) updateContentMode {
|
|
247
248
|
UIViewContentMode contentMode = (UIViewContentMode) _resizeMode;
|
|
248
|
-
if (_resizeMode ==
|
|
249
|
+
if (_resizeMode == FFFResizeModeNone) {
|
|
250
|
+
contentMode = UIViewContentModeCenter;
|
|
251
|
+
} else if (_resizeMode == RCTResizeModeRepeat) {
|
|
249
252
|
// The tiled image (see tiledImage:) fills the view.
|
|
250
253
|
contentMode = UIViewContentModeScaleToFill;
|
|
251
254
|
} else if (_resizeMode == RCTResizeModeCenter) {
|
|
@@ -803,9 +806,10 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
803
806
|
// Whether images are decoded at about the view's size (downsample, from
|
|
804
807
|
// SDWebImage 5.19, and always for photo library images, which are large and
|
|
805
808
|
// usually shown small: Photos makes them at the size asked for, on any
|
|
806
|
-
// version). Not for `repeat`, which tiles the image at its own size
|
|
809
|
+
// version). Not for `repeat`, which tiles the image at its own size, or
|
|
810
|
+
// `none`, which shows it at its own size.
|
|
807
811
|
- (BOOL) downsamples {
|
|
808
|
-
if (_resizeMode == RCTResizeModeRepeat) {
|
|
812
|
+
if (_resizeMode == RCTResizeModeRepeat || _resizeMode == FFFResizeModeNone) {
|
|
809
813
|
return NO;
|
|
810
814
|
}
|
|
811
815
|
return [_source isPhotoLibrary] || (_downsample && [FFFDownsampledImage isSupported]);
|
|
@@ -1009,6 +1013,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1009
1013
|
// Only on disk, also when it comes from there.
|
|
1010
1014
|
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
|
|
1011
1015
|
}
|
|
1016
|
+
[FFFDownsampledImage forgetSmallerFullSizeImageForURL: source.url context: context];
|
|
1012
1017
|
return context;
|
|
1013
1018
|
}
|
|
1014
1019
|
[FFFDownsampledImage addToContext: context box: box cover: cover];
|
|
@@ -103,7 +103,7 @@ RCT_EXPORT_MODULE(FastImageView)
|
|
|
103
103
|
RCT_EXPORT_VIEW_PROPERTY(source, FFFastImageSource)
|
|
104
104
|
RCT_EXPORT_VIEW_PROPERTY(sources, FFFastImageSourceArray)
|
|
105
105
|
RCT_EXPORT_VIEW_PROPERTY(defaultSource, UIImage)
|
|
106
|
-
RCT_EXPORT_VIEW_PROPERTY(resizeMode,
|
|
106
|
+
RCT_EXPORT_VIEW_PROPERTY(resizeMode, FFFResizeMode)
|
|
107
107
|
RCT_EXPORT_VIEW_PROPERTY(recyclingKey, NSString)
|
|
108
108
|
RCT_EXPORT_VIEW_PROPERTY(loopCount, NSInteger)
|
|
109
109
|
RCT_EXPORT_VIEW_PROPERTY(imageRendering, NSString)
|
|
@@ -288,6 +288,8 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
288
288
|
[results addObject:[NSNull null]];
|
|
289
289
|
SDWebImageOptions options = FFFPreloadOptions(source);
|
|
290
290
|
SDWebImageMutableContext *context = FFFPreloadContext(source);
|
|
291
|
+
// It looks in the memory cache first, for the full-size image.
|
|
292
|
+
[FFFDownsampledImage forgetSmallerFullSizeImageForURL:source.url context:context];
|
|
291
293
|
if (!source.memoryCache) {
|
|
292
294
|
// Not decoded (only its header is read, for the size), and not
|
|
293
295
|
// kept in memory, also when it comes from the disk cache.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#import <React/RCTConvert.h>
|
|
2
2
|
|
|
3
|
+
#import "FFFastImageView.h"
|
|
4
|
+
|
|
3
5
|
@class FFFastImageSource;
|
|
4
6
|
|
|
5
7
|
@interface RCTConvert (FFFastImage)
|
|
6
8
|
|
|
9
|
+
+ (FFFResizeMode)FFFResizeMode:(id)json;
|
|
7
10
|
+ (FFFastImageSource *)FFFastImageSource:(id)json;
|
|
8
11
|
+ (NSArray<FFFastImageSource *> *)FFFastImageSourceArray:(id)json;
|
|
9
12
|
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
@implementation RCTConvert (FFFastImage)
|
|
5
5
|
|
|
6
|
+
RCT_ENUM_CONVERTER(FFFResizeMode, (@{
|
|
7
|
+
@"cover": @(RCTResizeModeCover),
|
|
8
|
+
@"contain": @(RCTResizeModeContain),
|
|
9
|
+
@"stretch": @(RCTResizeModeStretch),
|
|
10
|
+
@"center": @(RCTResizeModeCenter),
|
|
11
|
+
// objectFit="scale-down": as center on iOS.
|
|
12
|
+
@"scale-down": @(RCTResizeModeCenter),
|
|
13
|
+
@"repeat": @(RCTResizeModeRepeat),
|
|
14
|
+
@"none": @(FFFResizeModeNone),
|
|
15
|
+
}), RCTResizeModeCover, integerValue);
|
|
16
|
+
|
|
6
17
|
RCT_ENUM_CONVERTER(FFFPriority, (@{
|
|
7
18
|
@"low": @(FFFPriorityLow),
|
|
8
19
|
@"normal": @(FFFPriorityNormal),
|