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.cjs.web.js
CHANGED
|
@@ -27,6 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
let react = require("react");
|
|
28
28
|
react = __toESM(react);
|
|
29
29
|
let react_native = require("react-native");
|
|
30
|
+
let react_native_web_dist_modules_AssetRegistry = require("react-native-web/dist/modules/AssetRegistry");
|
|
30
31
|
//#region src/constants.ts
|
|
31
32
|
const resizeMode = {
|
|
32
33
|
contain: "contain",
|
|
@@ -46,142 +47,279 @@ const cacheControl = {
|
|
|
46
47
|
cacheOnly: "cacheOnly"
|
|
47
48
|
};
|
|
48
49
|
//#endregion
|
|
50
|
+
//#region src/objectFit.ts
|
|
51
|
+
function fromStyle(style, key) {
|
|
52
|
+
if (Array.isArray(style)) {
|
|
53
|
+
for (let i = style.length - 1; i >= 0; i--) {
|
|
54
|
+
const found = fromStyle(style[i], key);
|
|
55
|
+
if (found !== void 0) return found;
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (typeof style === "number") {
|
|
60
|
+
const flattened = react_native.StyleSheet.flatten(style);
|
|
61
|
+
return flattened ? flattened[key] : void 0;
|
|
62
|
+
}
|
|
63
|
+
return style && typeof style === "object" ? style[key] : void 0;
|
|
64
|
+
}
|
|
65
|
+
const RESIZE_MODE_FIT = {
|
|
66
|
+
contain: "contain",
|
|
67
|
+
cover: "cover",
|
|
68
|
+
stretch: "fill",
|
|
69
|
+
center: "center",
|
|
70
|
+
repeat: "repeat"
|
|
71
|
+
};
|
|
72
|
+
const FITS = {
|
|
73
|
+
fill: true,
|
|
74
|
+
contain: true,
|
|
75
|
+
cover: true,
|
|
76
|
+
none: true,
|
|
77
|
+
"scale-down": true
|
|
78
|
+
};
|
|
79
|
+
function resolveObjectFit(objectFit, style, resizeMode) {
|
|
80
|
+
var _RESIZE_MODE_FIT$resi;
|
|
81
|
+
const fit = objectFit != null ? objectFit : fromStyle(style, "objectFit");
|
|
82
|
+
if (fit && FITS[fit] === true) return fit;
|
|
83
|
+
return (_RESIZE_MODE_FIT$resi = RESIZE_MODE_FIT[resizeMode]) !== null && _RESIZE_MODE_FIT$resi !== void 0 ? _RESIZE_MODE_FIT$resi : "cover";
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
49
86
|
//#region src/index.web.tsx
|
|
87
|
+
var _useId;
|
|
50
88
|
const notSupported = {
|
|
51
89
|
ok: false,
|
|
52
90
|
error: "Not supported on the web"
|
|
53
91
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
92
|
+
let ids = 0;
|
|
93
|
+
const useCounterId = () => (0, react.useState)(() => `${++ids}`)[0];
|
|
94
|
+
const useSvgId = (_useId = react.default.useId) !== null && _useId !== void 0 ? _useId : useCounterId;
|
|
95
|
+
const useClientLayoutEffect = globalThis.document === void 0 ? react.useEffect : react.useLayoutEffect;
|
|
96
|
+
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 }) {
|
|
97
|
+
const { accessibilityLabel, "aria-label": ariaLabel, ...viewProps } = props;
|
|
98
|
+
const label = ariaLabel || accessibilityLabel || void 0;
|
|
99
|
+
const several = Array.isArray(source) && source.length > 1 ? source : void 0;
|
|
100
|
+
const sized = several ? sizedSources(several) : void 0;
|
|
101
|
+
const src = sized ? sized.src : resolveUri(source);
|
|
102
|
+
const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
|
|
103
|
+
const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
|
|
104
|
+
const defaultUri = resolveUri(defaultSource);
|
|
105
|
+
const [result, setResult] = (0, react.useState)();
|
|
106
|
+
const ok = result && result.key === key ? result.ok : void 0;
|
|
107
|
+
const tintId = `fast-image-tint-${useSvgId().replace(/[^\w-]/g, "")}`;
|
|
108
|
+
const resolved = resolveObjectFit(objectFit, style, mode);
|
|
109
|
+
const fit = resolved === "center" ? "scale-down" : several && resolved === "repeat" ? "cover" : resolved;
|
|
110
|
+
const filter = [blurRadius ? `blur(${blurRadius}px)` : "", tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") || void 0;
|
|
111
|
+
const showDefault = !!defaultUri && ok !== true;
|
|
112
|
+
useClientLayoutEffect(() => {
|
|
113
|
+
if (key) onLoadStart === null || onLoadStart === void 0 || onLoadStart();
|
|
114
|
+
}, [key]);
|
|
62
115
|
return /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
116
|
+
...viewProps,
|
|
117
|
+
"aria-label": label,
|
|
63
118
|
style: [styles.container, style],
|
|
64
119
|
onLayout,
|
|
65
120
|
pointerEvents,
|
|
66
121
|
ref: forwardedRef
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
97
|
-
width,
|
|
98
|
-
height
|
|
99
|
-
} });
|
|
122
|
+
}, defaultUri && showDefault ? /* @__PURE__ */ react.default.createElement(Picture, {
|
|
123
|
+
key: `default ${defaultUri}`,
|
|
124
|
+
src: defaultUri,
|
|
125
|
+
fit,
|
|
126
|
+
filter,
|
|
127
|
+
alt: key ? "" : label !== null && label !== void 0 ? label : ""
|
|
128
|
+
}) : null, key && src ? /* @__PURE__ */ react.default.createElement(Picture, {
|
|
129
|
+
key: several ? "sizes" : key,
|
|
130
|
+
src,
|
|
131
|
+
srcSet,
|
|
132
|
+
lazy: !!several,
|
|
133
|
+
fit,
|
|
134
|
+
filter,
|
|
135
|
+
hidden: ok === false,
|
|
136
|
+
alt: label !== null && label !== void 0 ? label : "",
|
|
137
|
+
onLoad: (image) => {
|
|
138
|
+
const { width, height } = loadedSize(image, sized);
|
|
139
|
+
setResult({
|
|
140
|
+
key,
|
|
141
|
+
ok: true
|
|
142
|
+
});
|
|
143
|
+
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
144
|
+
width,
|
|
145
|
+
height
|
|
146
|
+
} });
|
|
147
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
148
|
+
ok: true,
|
|
149
|
+
width,
|
|
150
|
+
height
|
|
100
151
|
});
|
|
101
152
|
},
|
|
102
|
-
onError: (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
153
|
+
onError: (uri) => {
|
|
154
|
+
const error = `Failed to load resource ${uri}`;
|
|
155
|
+
setResult({
|
|
156
|
+
key,
|
|
157
|
+
ok: false
|
|
158
|
+
});
|
|
159
|
+
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
160
|
+
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
106
161
|
ok: false,
|
|
107
162
|
error
|
|
108
|
-
};
|
|
109
|
-
sent.current = Promise.resolve();
|
|
110
|
-
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
111
|
-
},
|
|
112
|
-
onLoadEnd: () => {
|
|
113
|
-
sent.current.then(() => {
|
|
114
|
-
var _result$current;
|
|
115
|
-
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd((_result$current = result.current) !== null && _result$current !== void 0 ? _result$current : {
|
|
116
|
-
ok: false,
|
|
117
|
-
error: "Failed to load the image"
|
|
118
|
-
});
|
|
119
|
-
result.current = void 0;
|
|
120
163
|
});
|
|
121
164
|
}
|
|
122
|
-
}),
|
|
165
|
+
}) : null, tintColor ? /* @__PURE__ */ react.default.createElement(TintFilter, {
|
|
166
|
+
id: tintId,
|
|
167
|
+
color: tintColor
|
|
168
|
+
}) : null, children);
|
|
169
|
+
}
|
|
170
|
+
function resolveUri(source) {
|
|
171
|
+
let uri;
|
|
172
|
+
if (typeof source === "number") {
|
|
173
|
+
var _asset$scales$;
|
|
174
|
+
const asset = (0, react_native_web_dist_modules_AssetRegistry.getAssetByID)(source);
|
|
175
|
+
if (!asset) return void 0;
|
|
176
|
+
const scale = 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);
|
|
177
|
+
const suffix = scale !== 1 ? `@${scale}x` : "";
|
|
178
|
+
uri = `${asset.httpServerLocation}/${asset.name}${suffix}.${asset.type}`;
|
|
179
|
+
} else if (typeof source === "string") uri = source;
|
|
180
|
+
else if (Array.isArray(source)) return resolveUri(source[0]);
|
|
181
|
+
else if (source && typeof source.uri === "string") uri = source.uri;
|
|
182
|
+
const svg = uri === null || uri === void 0 ? void 0 : uri.match(/^(data:image\/svg\+xml;utf8,)(.*)/);
|
|
183
|
+
return svg ? svg[1] + encodeURIComponent(svg[2]) : uri || void 0;
|
|
123
184
|
}
|
|
124
|
-
function
|
|
185
|
+
function sizedSources(sources) {
|
|
125
186
|
var _largest$uri, _sources$find;
|
|
126
|
-
const
|
|
187
|
+
const widths = sources.filter((source) => (source === null || source === void 0 ? void 0 : source.uri) && source.width).map((source) => {
|
|
127
188
|
var _source$width, _source$scale;
|
|
128
189
|
return {
|
|
129
190
|
uri: source.uri,
|
|
130
191
|
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))
|
|
131
192
|
};
|
|
132
193
|
});
|
|
133
|
-
const largest =
|
|
134
|
-
|
|
135
|
-
|
|
194
|
+
const largest = widths.reduce((best, source) => !best || source.width > best.width ? source : best, void 0);
|
|
195
|
+
return {
|
|
196
|
+
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,
|
|
197
|
+
srcSet: widths.length > 0 ? widths.map((source) => `${source.uri} ${source.width}w`).join(", ") : void 0,
|
|
198
|
+
widths
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function loadedSize(image, sized) {
|
|
202
|
+
var _picked$width;
|
|
203
|
+
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;
|
|
204
|
+
return {
|
|
205
|
+
width: (_picked$width = picked === null || picked === void 0 ? void 0 : picked.width) !== null && _picked$width !== void 0 ? _picked$width : image.naturalWidth,
|
|
206
|
+
height: picked ? Math.round(image.naturalHeight * picked.width / image.naturalWidth) : image.naturalHeight
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const fill = {
|
|
210
|
+
position: "absolute",
|
|
211
|
+
top: 0,
|
|
212
|
+
left: 0,
|
|
213
|
+
width: "100%",
|
|
214
|
+
height: "100%"
|
|
215
|
+
};
|
|
216
|
+
function Picture({ src, srcSet, lazy, fit, filter, hidden, alt, onLoad, onError }) {
|
|
217
|
+
const repeat = fit === "repeat";
|
|
218
|
+
const image = (0, react.useRef)(null);
|
|
219
|
+
const tiles = (0, react.useRef)(null);
|
|
220
|
+
const mounted = (0, react.useRef)(true);
|
|
221
|
+
const handled = (0, react.useRef)(void 0);
|
|
222
|
+
const [natural, setNatural] = (0, react.useState)();
|
|
223
|
+
const [box, setBox] = (0, react.useState)();
|
|
224
|
+
const loaded = (target) => {
|
|
225
|
+
var _handled$current, _target$decode;
|
|
226
|
+
const file = target.currentSrc || src;
|
|
227
|
+
const sources = srcSet !== null && srcSet !== void 0 ? srcSet : src;
|
|
228
|
+
if (!mounted.current || ((_handled$current = handled.current) === null || _handled$current === void 0 ? void 0 : _handled$current.sources) === sources && handled.current.file === file) return;
|
|
229
|
+
handled.current = {
|
|
230
|
+
sources,
|
|
231
|
+
file
|
|
232
|
+
};
|
|
233
|
+
setNatural({
|
|
234
|
+
width: target.naturalWidth,
|
|
235
|
+
height: target.naturalHeight
|
|
236
|
+
});
|
|
237
|
+
const done = () => {
|
|
238
|
+
if (mounted.current) onLoad === null || onLoad === void 0 || onLoad(target);
|
|
239
|
+
};
|
|
240
|
+
Promise.resolve((_target$decode = target.decode) === null || _target$decode === void 0 ? void 0 : _target$decode.call(target)).then(done, done);
|
|
241
|
+
};
|
|
136
242
|
(0, react.useEffect)(() => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
243
|
+
mounted.current = true;
|
|
244
|
+
return () => {
|
|
245
|
+
mounted.current = false;
|
|
246
|
+
};
|
|
247
|
+
}, []);
|
|
248
|
+
useClientLayoutEffect(() => {
|
|
249
|
+
var _target$decode2;
|
|
250
|
+
const target = image.current;
|
|
251
|
+
if (!(target === null || target === void 0 ? void 0 : target.complete)) return;
|
|
252
|
+
if (target.naturalWidth > 0) return loaded(target);
|
|
253
|
+
Promise.resolve((_target$decode2 = target.decode) === null || _target$decode2 === void 0 ? void 0 : _target$decode2.call(target)).then(() => loaded(target), () => {
|
|
254
|
+
if (mounted.current) onError === null || onError === void 0 || onError(target.currentSrc || src);
|
|
255
|
+
});
|
|
256
|
+
}, []);
|
|
257
|
+
useClientLayoutEffect(() => {
|
|
258
|
+
const Observer = globalThis.ResizeObserver;
|
|
259
|
+
const node = tiles.current;
|
|
260
|
+
if (!repeat || !node || !Observer) return;
|
|
261
|
+
const observer = new Observer((entries) => {
|
|
262
|
+
const { width, height } = entries[0].contentRect;
|
|
263
|
+
setBox({
|
|
264
|
+
width,
|
|
265
|
+
height
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
observer.observe(node);
|
|
269
|
+
return () => observer.disconnect();
|
|
270
|
+
}, [repeat]);
|
|
271
|
+
const img = /* @__PURE__ */ react.default.createElement("img", {
|
|
272
|
+
ref: image,
|
|
140
273
|
src,
|
|
141
274
|
srcSet,
|
|
142
275
|
sizes: srcSet ? "auto, 100vw" : void 0,
|
|
143
|
-
loading: "lazy",
|
|
144
|
-
alt
|
|
276
|
+
loading: lazy ? "lazy" : void 0,
|
|
277
|
+
alt,
|
|
145
278
|
draggable: false,
|
|
146
279
|
style: {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
filter: blurRadius ? `blur(${blurRadius}px)` : void 0
|
|
280
|
+
...fill,
|
|
281
|
+
objectFit: repeat ? "cover" : fit,
|
|
282
|
+
filter: repeat ? void 0 : filter,
|
|
283
|
+
opacity: repeat || hidden ? 0 : void 0
|
|
152
284
|
},
|
|
153
|
-
onLoad: (event) =>
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
const loadedWidth = (_picked$width = picked === null || picked === void 0 ? void 0 : picked.width) !== null && _picked$width !== void 0 ? _picked$width : image.naturalWidth;
|
|
158
|
-
const loadedHeight = picked ? Math.round(image.naturalHeight * picked.width / image.naturalWidth) : image.naturalHeight;
|
|
159
|
-
onLoad === null || onLoad === void 0 || onLoad({ nativeEvent: {
|
|
160
|
-
width: loadedWidth,
|
|
161
|
-
height: loadedHeight
|
|
162
|
-
} });
|
|
163
|
-
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
164
|
-
ok: true,
|
|
165
|
-
width: loadedWidth,
|
|
166
|
-
height: loadedHeight
|
|
167
|
-
});
|
|
168
|
-
},
|
|
169
|
-
onError: () => {
|
|
170
|
-
const error = "Failed to load the image";
|
|
171
|
-
onError === null || onError === void 0 || onError({ nativeEvent: { error } });
|
|
172
|
-
onLoadEnd === null || onLoadEnd === void 0 || onLoadEnd({
|
|
173
|
-
ok: false,
|
|
174
|
-
error
|
|
175
|
-
});
|
|
285
|
+
onLoad: (event) => loaded(event.currentTarget),
|
|
286
|
+
onError: (event) => {
|
|
287
|
+
const target = event.currentTarget;
|
|
288
|
+
if (mounted.current) onError === null || onError === void 0 || onError((target === null || target === void 0 ? void 0 : target.currentSrc) || src);
|
|
176
289
|
}
|
|
177
290
|
});
|
|
291
|
+
return /* @__PURE__ */ react.default.createElement(react.default.Fragment, null, repeat ? /* @__PURE__ */ react.default.createElement("div", {
|
|
292
|
+
ref: tiles,
|
|
293
|
+
style: {
|
|
294
|
+
...fill,
|
|
295
|
+
backgroundImage: `url("${src}")`,
|
|
296
|
+
backgroundRepeat: "repeat",
|
|
297
|
+
backgroundPosition: "0",
|
|
298
|
+
backgroundSize: tileSize(natural, box),
|
|
299
|
+
filter,
|
|
300
|
+
opacity: hidden ? 0 : void 0
|
|
301
|
+
}
|
|
302
|
+
}) : null, img);
|
|
178
303
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
304
|
+
function tileSize(natural, box) {
|
|
305
|
+
if (!(natural === null || natural === void 0 ? void 0 : natural.width) || !natural.height || !(box === null || box === void 0 ? void 0 : box.width) || !box.height) return;
|
|
306
|
+
const scale = Math.min(1, box.width / natural.width, box.height / natural.height);
|
|
307
|
+
return `${Math.ceil(scale * natural.width)}px ${Math.ceil(scale * natural.height)}px`;
|
|
308
|
+
}
|
|
309
|
+
function TintFilter({ id, color }) {
|
|
310
|
+
return /* @__PURE__ */ react.default.createElement("svg", { style: tintStyle }, /* @__PURE__ */ react.default.createElement("defs", null, /* @__PURE__ */ react.default.createElement("filter", { id }, /* @__PURE__ */ react.default.createElement("feFlood", {
|
|
311
|
+
floodColor: String(color),
|
|
312
|
+
key: String(color)
|
|
313
|
+
}), /* @__PURE__ */ react.default.createElement("feComposite", {
|
|
314
|
+
in2: "SourceAlpha",
|
|
315
|
+
operator: "in"
|
|
316
|
+
}))));
|
|
317
|
+
}
|
|
318
|
+
const tintStyle = {
|
|
319
|
+
position: "absolute",
|
|
320
|
+
width: 0,
|
|
321
|
+
height: 0,
|
|
322
|
+
visibility: "hidden"
|
|
185
323
|
};
|
|
186
324
|
const FastImageMemo = (0, react.memo)(FastImageBase);
|
|
187
325
|
const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
|
|
@@ -189,24 +327,6 @@ const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */
|
|
|
189
327
|
...props
|
|
190
328
|
}));
|
|
191
329
|
FastImageComponent.displayName = "FastImage";
|
|
192
|
-
async function loadedSize(loaded, view, uri) {
|
|
193
|
-
var _view$querySelector;
|
|
194
|
-
if (loaded === null || loaded === void 0 ? void 0 : loaded.naturalWidth) return {
|
|
195
|
-
width: loaded.naturalWidth,
|
|
196
|
-
height: loaded.naturalHeight
|
|
197
|
-
};
|
|
198
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
199
|
-
const shown = view === null || view === void 0 || (_view$querySelector = view.querySelector) === null || _view$querySelector === void 0 ? void 0 : _view$querySelector.call(view, "img");
|
|
200
|
-
if (shown === null || shown === void 0 ? void 0 : shown.naturalWidth) return {
|
|
201
|
-
width: shown.naturalWidth,
|
|
202
|
-
height: shown.naturalHeight
|
|
203
|
-
};
|
|
204
|
-
const src = uri !== null && uri !== void 0 ? uri : shown === null || shown === void 0 ? void 0 : shown.src;
|
|
205
|
-
return src ? getSize(src) : {
|
|
206
|
-
width: 0,
|
|
207
|
-
height: 0
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
330
|
function getSize(uri) {
|
|
211
331
|
return new Promise((resolve) => {
|
|
212
332
|
react_native.Image.getSize(uri, (width, height) => resolve({
|
|
@@ -244,16 +364,7 @@ FastImage.clearDiskCache = () => Promise.resolve(notSupported);
|
|
|
244
364
|
FastImage.configureCache = () => Promise.resolve({});
|
|
245
365
|
FastImage.writeToCache = () => Promise.resolve(notSupported);
|
|
246
366
|
FastImage.getCachePath = () => Promise.resolve(notSupported);
|
|
247
|
-
const styles = react_native.StyleSheet.create({
|
|
248
|
-
container: { overflow: "hidden" },
|
|
249
|
-
image: {
|
|
250
|
-
position: "absolute",
|
|
251
|
-
top: 0,
|
|
252
|
-
left: 0,
|
|
253
|
-
width: "100%",
|
|
254
|
-
height: "100%"
|
|
255
|
-
}
|
|
256
|
-
});
|
|
367
|
+
const styles = react_native.StyleSheet.create({ container: { overflow: "hidden" } });
|
|
257
368
|
const FastImageWithRef = FastImageComponent;
|
|
258
369
|
const FastImageBackground = (0, react.forwardRef)(({ style, imageStyle, imageRef, children, ...props }, ref) => /* @__PURE__ */ react.default.createElement(react_native.View, {
|
|
259
370
|
style,
|
|
@@ -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,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ColorValue, View, StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
|
|
3
3
|
import { cacheControl, priority, resizeMode } from './constants';
|
|
4
|
+
/** How the image fits the view: see [`objectFit`](#objectfit). */
|
|
5
|
+
export type ObjectFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down';
|
|
4
6
|
/** How the image fits the view: see [`resizeMode`](#resizemode). */
|
|
5
7
|
export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center' | 'repeat';
|
|
6
8
|
/** An image's load priority: see [`source.priority`](#sourcepriority). */
|
|
@@ -234,6 +236,7 @@ export interface OnProgressEvent {
|
|
|
234
236
|
export interface ImageStyle extends ViewStyle {
|
|
235
237
|
overlayColor?: ColorValue;
|
|
236
238
|
tintColor?: ColorValue;
|
|
239
|
+
objectFit?: ObjectFit;
|
|
237
240
|
}
|
|
238
241
|
export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
239
242
|
/**
|
|
@@ -287,22 +290,46 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
287
290
|
*/
|
|
288
291
|
defaultSource?: ImageRequireSource;
|
|
289
292
|
/**
|
|
290
|
-
* How the image
|
|
293
|
+
* How the image fits the view, as CSS's `object-fit` does.
|
|
291
294
|
*
|
|
292
|
-
* - `'contain'`: scales it uniformly (keeping its aspect ratio) so all of
|
|
293
|
-
* it fits in the view (minus padding).
|
|
294
295
|
* - `'cover'`: scales it uniformly (keeping its aspect ratio) so it covers
|
|
295
296
|
* the view (minus padding), cropping what doesn't fit.
|
|
296
|
-
* - `'
|
|
297
|
+
* - `'contain'`: scales it uniformly (keeping its aspect ratio) so all of
|
|
298
|
+
* it fits in the view (minus padding).
|
|
299
|
+
* - `'fill'`: scales its width and height separately to fill the view,
|
|
297
300
|
* which can change its aspect ratio.
|
|
298
|
-
* - `'
|
|
299
|
-
*
|
|
301
|
+
* - `'none'`: shows it at its own size, centered, cropped if it's larger
|
|
302
|
+
* than the view.
|
|
303
|
+
* - `'scale-down'`: shows it at its own size, centered, or scaled down
|
|
304
|
+
* uniformly to fit if it's larger than the view (the smaller of `'none'`
|
|
305
|
+
* and `'contain'`).
|
|
306
|
+
*
|
|
307
|
+
* An image's own size is its size in pixels, as points (dp on Android),
|
|
308
|
+
* as CSS counts an image's pixels: a 300 × 200 image is 300 × 200 points.
|
|
309
|
+
* A bundled image (`require()`) is its size in points.
|
|
310
|
+
*
|
|
311
|
+
* It can also be set in `style`; the prop wins. Either one overrides
|
|
312
|
+
* `resizeMode`.
|
|
313
|
+
*
|
|
314
|
+
* @default 'cover'
|
|
315
|
+
*/
|
|
316
|
+
objectFit?: ObjectFit;
|
|
317
|
+
/**
|
|
318
|
+
* How the image fills the view.
|
|
319
|
+
*
|
|
320
|
+
* - `'contain'`: as `objectFit="contain"`.
|
|
321
|
+
* - `'cover'`: as `objectFit="cover"`.
|
|
322
|
+
* - `'stretch'`: as `objectFit="fill"`.
|
|
323
|
+
* - `'center'`: as `objectFit="scale-down"`, except that on Android an
|
|
324
|
+
* image smaller than the view is shown at its size in pixels on the
|
|
325
|
+
* screen, so smaller than on iOS and the web.
|
|
300
326
|
* - `'repeat'`: repeats it to cover the view, from its top-left corner, at
|
|
301
327
|
* the image's own size in pixels (a bundled image at its size in
|
|
302
328
|
* points), scaled down to fit if it's larger than the view. An animated
|
|
303
329
|
* image repeats its first frame, and `defaultSource` repeats too.
|
|
304
330
|
*
|
|
305
331
|
* @default 'cover'
|
|
332
|
+
* @deprecated Use `objectFit` instead.
|
|
306
333
|
*/
|
|
307
334
|
resizeMode?: ResizeMode;
|
|
308
335
|
/**
|
|
@@ -539,6 +566,9 @@ export interface PreloadFailure {
|
|
|
539
566
|
*/
|
|
540
567
|
export type PreloadResult = PreloadSuccess | PreloadFailure;
|
|
541
568
|
export interface FastImageStaticProperties {
|
|
569
|
+
/**
|
|
570
|
+
* @deprecated Use the `objectFit` prop instead of `resizeMode`.
|
|
571
|
+
*/
|
|
542
572
|
resizeMode: typeof resizeMode;
|
|
543
573
|
priority: typeof priority;
|
|
544
574
|
cacheControl: typeof cacheControl;
|
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: use objectFit.
|
|
126
129
|
resizeMode?: ?ResizeModes,
|
|
127
130
|
fallback?: ?boolean,
|
|
128
131
|
recyclingKey?: ?string,
|