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.
Files changed (28) hide show
  1. package/README.md +36 -7
  2. package/android/src/main/java/com/dylanvann/fastimage/FastImageBlur.java +18 -5
  3. package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +5 -1
  4. package/android/src/main/java/com/dylanvann/fastimage/FastImageSharedDownloads.java +64 -19
  5. package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +22 -0
  6. package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +27 -0
  7. package/android/src/main/java/com/dylanvann/fastimage/FastImageSvgRenderer.java +10 -0
  8. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +5 -0
  9. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -1
  10. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +157 -14
  11. package/dist/index.cjs.js +50 -18
  12. package/dist/index.cjs.js.flow +3 -0
  13. package/dist/index.cjs.web.js +241 -130
  14. package/dist/index.cjs.web.js.flow +3 -0
  15. package/dist/index.d.ts +36 -6
  16. package/dist/index.js +50 -18
  17. package/dist/index.js.flow +3 -0
  18. package/dist/index.web.js +243 -132
  19. package/dist/index.web.js.flow +3 -0
  20. package/dist/objectFit.d.ts +3 -0
  21. package/ios/FastImage/FFFDownsampledImage.h +8 -0
  22. package/ios/FastImage/FFFDownsampledImage.m +16 -0
  23. package/ios/FastImage/FFFastImageView.h +7 -1
  24. package/ios/FastImage/FFFastImageView.m +10 -5
  25. package/ios/FastImage/FFFastImageViewManager.m +3 -1
  26. package/ios/FastImage/RCTConvert+FFFastImage.h +3 -0
  27. package/ios/FastImage/RCTConvert+FFFastImage.m +11 -0
  28. package/package.json +1 -1
@@ -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
- function FastImageBase({ source, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, resizeMode: mode = "cover", forwardedRef, onLayout, pointerEvents, fallback: _fallback, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
55
- const result = (0, react.useRef)(void 0);
56
- const sent = (0, react.useRef)(Promise.resolve());
57
- const image = (0, react.useRef)(null);
58
- const sizes = Array.isArray(source) && source.length > 1 ? source : void 0;
59
- const single = Array.isArray(source) ? source[0] : source;
60
- const uri = typeof single === "object" && single ? single.uri : void 0;
61
- const webSource = typeof single === "number" ? single : uri ? { uri } : void 0;
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
- }, sizes ? /* @__PURE__ */ react.default.createElement(react_native.View, {
68
- ...props,
69
- style: styles.image
70
- }, /* @__PURE__ */ react.default.createElement(ImageSizes, {
71
- sources: sizes,
72
- resizeMode: mode,
73
- blurRadius,
74
- onLoadStart,
75
- onLoad,
76
- onError,
77
- onLoadEnd
78
- })) : /* @__PURE__ */ react.default.createElement(react_native.Image, {
79
- ...props,
80
- style: styles.image,
81
- source: webSource,
82
- defaultSource,
83
- resizeMode: mode,
84
- tintColor,
85
- blurRadius,
86
- ref: image,
87
- onLoadStart,
88
- onLoad: (event) => {
89
- var _event$nativeEvent;
90
- sent.current = loadedSize(event === null || event === void 0 || (_event$nativeEvent = event.nativeEvent) === null || _event$nativeEvent === void 0 ? void 0 : _event$nativeEvent.target, image.current, uri).then(({ width, height }) => {
91
- result.current = {
92
- ok: true,
93
- width,
94
- height
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: (event) => {
103
- var _event$nativeEvent$er, _event$nativeEvent2;
104
- const error = String((_event$nativeEvent$er = event === null || event === void 0 || (_event$nativeEvent2 = event.nativeEvent) === null || _event$nativeEvent2 === void 0 ? void 0 : _event$nativeEvent2.error) !== null && _event$nativeEvent$er !== void 0 ? _event$nativeEvent$er : "Failed to load the image");
105
- result.current = {
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
- }), children);
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 ImageSizes({ sources, resizeMode: mode, blurRadius, onLoadStart, onLoad, onError, onLoadEnd }) {
185
+ function sizedSources(sources) {
125
186
  var _largest$uri, _sources$find;
126
- const sized = sources.filter((source) => (source === null || source === void 0 ? void 0 : source.uri) && source.width).map((source) => {
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 = sized.reduce((best, source) => !best || source.width > best.width ? source : best, void 0);
134
- const 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;
135
- const srcSet = sized.length > 0 ? sized.map((source) => `${source.uri} ${source.width}w`).join(", ") : void 0;
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
- onLoadStart === null || onLoadStart === void 0 || onLoadStart();
138
- }, [src, srcSet]);
139
- return /* @__PURE__ */ react.default.createElement("img", {
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
- position: "absolute",
148
- width: "100%",
149
- height: "100%",
150
- objectFit: OBJECT_FIT[mode !== null && mode !== void 0 ? mode : "cover"],
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
- var _picked$width;
155
- const image = event.currentTarget;
156
- const picked = srcSet ? sized.find((source) => new URL(source.uri, image.currentSrc).href === image.currentSrc) : void 0;
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
- const OBJECT_FIT = {
180
- cover: "cover",
181
- contain: "contain",
182
- stretch: "fill",
183
- center: "scale-down",
184
- repeat: "cover"
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 fills the view.
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
- * - `'stretch'`: scales its width and height separately to fill the view,
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
- * - `'center'`: centers it at its own size, scaled down uniformly to fit
299
- * if it's larger than the view.
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/index.tsx
23
- const resolveDefaultSource = (defaultSource) => {
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 = tintColorFromStyle(style[i]);
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.tintColor : void 0;
33
+ return flattened ? flattened[key] : void 0;
43
34
  }
44
- return style && typeof style === "object" ? style.tintColor : void 0;
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 : tintColorFromStyle(style);
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);
@@ -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,