react-native-fast-image 9.2.1 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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: objectFit, except for 'repeat'.
126
129
  resizeMode?: ?ResizeModes,
127
130
  fallback?: ?boolean,
128
131
  recyclingKey?: ?string,
package/dist/index.web.js CHANGED
@@ -1,5 +1,6 @@
1
- import React, { forwardRef, memo, useEffect, useRef } from "react";
2
- import { Image, StyleSheet, View } from "react-native";
1
+ import React, { forwardRef, memo, useEffect, useLayoutEffect, useRef, useState } from "react";
2
+ import { Image, PixelRatio, StyleSheet, View } from "react-native";
3
+ import { getAssetByID } from "react-native-web/dist/modules/AssetRegistry";
3
4
  //#region src/constants.ts
4
5
  const resizeMode = {
5
6
  contain: "contain",
@@ -19,142 +20,279 @@ const cacheControl = {
19
20
  cacheOnly: "cacheOnly"
20
21
  };
21
22
  //#endregion
23
+ //#region src/objectFit.ts
24
+ function fromStyle(style, key) {
25
+ if (Array.isArray(style)) {
26
+ for (let i = style.length - 1; i >= 0; i--) {
27
+ const found = fromStyle(style[i], key);
28
+ if (found !== void 0) return found;
29
+ }
30
+ return;
31
+ }
32
+ if (typeof style === "number") {
33
+ const flattened = StyleSheet.flatten(style);
34
+ return flattened ? flattened[key] : void 0;
35
+ }
36
+ return style && typeof style === "object" ? style[key] : void 0;
37
+ }
38
+ const RESIZE_MODE_FIT = {
39
+ contain: "contain",
40
+ cover: "cover",
41
+ stretch: "fill",
42
+ center: "center",
43
+ repeat: "repeat"
44
+ };
45
+ const FITS = {
46
+ fill: true,
47
+ contain: true,
48
+ cover: true,
49
+ none: true,
50
+ "scale-down": true
51
+ };
52
+ function resolveObjectFit(objectFit, style, resizeMode) {
53
+ var _RESIZE_MODE_FIT$resi;
54
+ const fit = objectFit != null ? objectFit : fromStyle(style, "objectFit");
55
+ if (fit && FITS[fit] === true) return fit;
56
+ return (_RESIZE_MODE_FIT$resi = RESIZE_MODE_FIT[resizeMode]) !== null && _RESIZE_MODE_FIT$resi !== void 0 ? _RESIZE_MODE_FIT$resi : "cover";
57
+ }
58
+ //#endregion
22
59
  //#region src/index.web.tsx
60
+ var _useId;
23
61
  const notSupported = {
24
62
  ok: false,
25
63
  error: "Not supported on the web"
26
64
  };
27
- 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 }) {
28
- const result = useRef(void 0);
29
- const sent = useRef(Promise.resolve());
30
- const image = useRef(null);
31
- const sizes = Array.isArray(source) && source.length > 1 ? source : void 0;
32
- const single = Array.isArray(source) ? source[0] : source;
33
- const uri = typeof single === "object" && single ? single.uri : void 0;
34
- const webSource = typeof single === "number" ? single : uri ? { uri } : void 0;
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
- }, sizes ? /* @__PURE__ */ React.createElement(View, {
41
- ...props,
42
- style: styles.image
43
- }, /* @__PURE__ */ React.createElement(ImageSizes, {
44
- sources: sizes,
45
- resizeMode: mode,
46
- blurRadius,
47
- onLoadStart,
48
- onLoad,
49
- onError,
50
- onLoadEnd
51
- })) : /* @__PURE__ */ React.createElement(Image, {
52
- ...props,
53
- style: styles.image,
54
- source: webSource,
55
- defaultSource,
56
- resizeMode: mode,
57
- tintColor,
58
- blurRadius,
59
- ref: image,
60
- onLoadStart,
61
- onLoad: (event) => {
62
- var _event$nativeEvent;
63
- 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 }) => {
64
- result.current = {
65
- ok: true,
66
- width,
67
- height
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: (event) => {
76
- var _event$nativeEvent$er, _event$nativeEvent2;
77
- 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");
78
- result.current = {
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
- }), children);
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 ImageSizes({ sources, resizeMode: mode, blurRadius, onLoadStart, onLoad, onError, onLoadEnd }) {
158
+ function sizedSources(sources) {
98
159
  var _largest$uri, _sources$find;
99
- const sized = sources.filter((source) => (source === null || source === void 0 ? void 0 : source.uri) && source.width).map((source) => {
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 = sized.reduce((best, source) => !best || source.width > best.width ? source : best, void 0);
107
- 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;
108
- const srcSet = sized.length > 0 ? sized.map((source) => `${source.uri} ${source.width}w`).join(", ") : void 0;
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
- onLoadStart === null || onLoadStart === void 0 || onLoadStart();
111
- }, [src, srcSet]);
112
- return /* @__PURE__ */ React.createElement("img", {
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
- position: "absolute",
121
- width: "100%",
122
- height: "100%",
123
- objectFit: OBJECT_FIT[mode !== null && mode !== void 0 ? mode : "cover"],
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
- var _picked$width;
128
- const image = event.currentTarget;
129
- const picked = srcSet ? sized.find((source) => new URL(source.uri, image.currentSrc).href === image.currentSrc) : void 0;
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
- const OBJECT_FIT = {
153
- cover: "cover",
154
- contain: "contain",
155
- stretch: "fill",
156
- center: "scale-down",
157
- repeat: "cover"
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,
@@ -49,6 +49,7 @@ export type CacheControl = $ReadOnly<{|
49
49
  |}>
50
50
 
51
51
  export type ResizeModes = $Values<ResizeMode>
52
+ export type ObjectFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
52
53
  export type Priorities = $Values<Priority>
53
54
  export type CacheControls = $Values<CacheControl>
54
55
 
@@ -123,6 +124,8 @@ export type FastImageProps = $ReadOnly<{|
123
124
  defaultSource?: ?number,
124
125
 
125
126
  tintColor?: number | string,
127
+ objectFit?: ?ObjectFit,
128
+ // Deprecated: objectFit, except for 'repeat'.
126
129
  resizeMode?: ?ResizeModes,
127
130
  fallback?: ?boolean,
128
131
  recyclingKey?: ?string,
@@ -0,0 +1,3 @@
1
+ import type { ImageStyle, ObjectFit, ResizeMode } from './index';
2
+ export declare function fromStyle<K extends 'tintColor' | 'objectFit'>(style: unknown, key: K): ImageStyle[K];
3
+ export declare function resolveObjectFit(objectFit: ObjectFit | null | undefined, style: unknown, resizeMode: ResizeMode): ObjectFit | 'repeat' | 'center';
@@ -25,6 +25,14 @@
25
25
  // loaded this way was decoded from, for onLoad, or zero for other images.
26
26
  + (CGSize) sourceSizeOfImage: (UIImage*)image;
27
27
 
28
+ // SDWebImage puts an image it decoded smaller from the disk cache, for a view
29
+ // that downsamples, into the memory cache under the full-size image's key too
30
+ // (SDWebImageManager writes what its original cache query finds back there),
31
+ // where a full-size load (a view that doesn't downsample, or a preload) would
32
+ // find it: a blurry image, or a preload's size. Drops it there, so that load
33
+ // decodes the file from the disk cache. Call it before a full-size load.
34
+ + (void) forgetSmallerFullSizeImageForURL: (NSURL*)url context: (SDWebImageContext*)context;
35
+
28
36
  // Whether this version of SDWebImage can decode through this class: it
29
37
  // decodes static images through the animated image class since 5.19.
30
38
  + (BOOL) isSupported;
@@ -5,6 +5,8 @@
5
5
  #import <SDWebImage/UIImage+Metadata.h>
6
6
  #import <SDWebImage/NSData+ImageContentType.h>
7
7
  #import <SDWebImage/SDImageCodersManager.h>
8
+ #import <SDWebImage/SDImageCache.h>
9
+ #import <SDWebImage/SDWebImageManager.h>
8
10
 
9
11
  // The decode option that carries the full image's size to the view.
10
12
  static SDImageCoderOption const FFFDecodeSourceSize = @"FFFDecodeSourceSize";
@@ -109,6 +111,20 @@ static CGSize FFFPixelSize(NSData* data) {
109
111
  return [sourceSize isKindOfClass: [FFFSourceSize class]] ? ((FFFSourceSize*) sourceSize).size : CGSizeZero;
110
112
  }
111
113
 
114
+ + (void) forgetSmallerFullSizeImageForURL: (NSURL*)url context: (SDWebImageContext*)context {
115
+ SDWebImageManager* manager = [SDWebImageManager sharedManager];
116
+ if (!url || ![manager.imageCache isKindOfClass: [SDImageCache class]] || ![manager respondsToSelector: @selector(cacheKeyForURL:context:)]) {
117
+ return;
118
+ }
119
+ SDImageCache* cache = (SDImageCache*) manager.imageCache;
120
+ NSString* key = [manager cacheKeyForURL: url context: context];
121
+ UIImage* image = [cache imageFromMemoryCacheForKey: key];
122
+ CGSize fullSize = [self sourceSizeOfImage: image];
123
+ if (image && fullSize.width > 0 && fullSize.height > 0 && !CGSizeEqualToSize(image.size, fullSize)) {
124
+ [cache removeImageFromMemoryForKey: key];
125
+ }
126
+ }
127
+
112
128
  - (instancetype) initWithData: (NSData*)data scale: (CGFloat)scale options: (SDImageCoderOptions*)options {
113
129
  // An SVG isn't decoded smaller: it's a vector image, drawn at any size.
114
130
  // This class can't decode it, and SDWebImage would then give it to the
@@ -8,6 +8,12 @@
8
8
 
9
9
  #import "FFFastImageSource.h"
10
10
 
11
+ // resizeMode: React Native's, and `none` (objectFit="none": the image at its
12
+ // own size, centered, not scaled), which React Native's own resize modes only
13
+ // have from 0.77, with another meaning (top-left).
14
+ typedef RCTResizeMode FFFResizeMode;
15
+ static const FFFResizeMode FFFResizeModeNone = (FFFResizeMode) -2;
16
+
11
17
  @interface FFFastImageView : SDAnimatedImageView
12
18
 
13
19
  @property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadStart;
@@ -17,7 +23,7 @@
17
23
  @property (nonatomic, copy) RCTDirectEventBlock onFastImageError;
18
24
  @property (nonatomic, copy) RCTDirectEventBlock onFastImageLoad;
19
25
  @property (nonatomic, copy) RCTDirectEventBlock onFastImageLoadEnd;
20
- @property (nonatomic, assign) RCTResizeMode resizeMode;
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.