UNPKG

react-native-fast-image

Version:
271 lines (270 loc) • 9.75 kB
import React, { forwardRef, memo, useRef } from "react"; import { Image, NativeModules, Platform, StyleSheet, View, requireNativeComponent } from "react-native"; //#region src/constants.ts const resizeMode = { contain: "contain", cover: "cover", stretch: "stretch", center: "center", repeat: "repeat" }; const priority = { low: "low", normal: "normal", high: "high" }; const cacheControl = { immutable: "immutable", web: "web", cacheOnly: "cacheOnly" }; //#endregion //#region src/index.tsx const resolveDefaultSource = (defaultSource) => { if (!defaultSource) return null; if (Platform.OS === "android") { const resolved = Image.resolveAssetSource(defaultSource); if (resolved) return resolved.uri; return null; } return defaultSource; }; function tintColorFromStyle(style) { if (Array.isArray(style)) { for (let i = style.length - 1; i >= 0; i--) { const found = tintColorFromStyle(style[i]); if (found !== void 0) return found; } return; } if (typeof style === "number") { const flattened = StyleSheet.flatten(style); return flattened ? flattened.tintColor : void 0; } return style && typeof style === "object" ? style.tintColor : void 0; } function loopCount(loop) { if (loop === void 0) return -1; if (loop === true) return 0; if (loop === false) return 1; return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0; } function loadResult(event) { var _event$width, _event$height, _event$error; return event.ok ? { ok: true, width: (_event$width = event.width) !== null && _event$width !== void 0 ? _event$width : 0, height: (_event$height = event.height) !== null && _event$height !== void 0 ? _event$height : 0 } : { ok: false, error: (_event$error = event.error) !== null && _event$error !== void 0 ? _event$error : "Failed to load the image" }; } function withProgress(onProgress) { return onProgress && ((event) => { const { loaded, total } = event.nativeEvent; event.nativeEvent.progress = total > 0 ? Math.min(1, Math.max(0, loaded / total)) : 0; onProgress(event); }); } const DEFAULT_FADE_MS = Platform.OS === "ios" ? 250 : 300; function transitionProps(transition) { var _transition$duration; const { duration, betweenImages, skipOnCacheHit } = typeof transition === "number" ? { duration: transition } : typeof transition === "object" && transition ? { ...transition, duration: (_transition$duration = transition.duration) !== null && _transition$duration !== void 0 ? _transition$duration : DEFAULT_FADE_MS } : { duration: transition ? DEFAULT_FADE_MS : 0 }; return { transitionDuration: typeof duration === "number" && duration > 0 ? duration : 0, transitionBetweenImages: !!betweenImages, transitionSkipOnCacheHit: skipOnCacheHit || "memory" }; } function withoutCache(source) { const { cache: _cache, ...rest } = source || {}; return rest; } 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 }) { const { onClick, ...props } = viewProps; const fallbackResult = useRef(void 0); const wrapperProps = { onLayout, onClick, pointerEvents, hitSlop, onStartShouldSetResponder, onStartShouldSetResponderCapture, onMoveShouldSetResponder, onMoveShouldSetResponderCapture, onResponderGrant, onResponderReject, onResponderStart, onResponderMove, onResponderEnd, onResponderRelease, onResponderTerminationRequest, onResponderTerminate, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, onTouchEndCapture }; const imageProps = { ...props, pointerEvents: pointerEvents === "box-none" ? "none" : void 0 }; const resolvedTintColor = tintColor != null ? tintColor : tintColorFromStyle(style); if (fallback) { const cleanedSource = typeof source === "number" ? source : Array.isArray(source) ? source.map(withoutCache) : withoutCache(source); const resolvedSource = Image.resolveAssetSource(cleanedSource); return /* @__PURE__ */ React.createElement(View, { style: [styles.imageContainer, style], ...wrapperProps, ref: forwardedRef }, /* @__PURE__ */ React.createElement(Image, { ...imageProps, style: [styles.fallbackImage, { tintColor: resolvedTintColor }], source: resolvedSource, defaultSource, onLoadStart, onProgress: withProgress(onProgress), onLoad: onLoadEnd ? (event) => { const { width, height } = event.nativeEvent.source; fallbackResult.current = { ok: true, width, height }; onLoad === null || onLoad === void 0 || onLoad(event); } : onLoad, onError: onLoadEnd ? (event) => { fallbackResult.current = { ok: false, error: String(event.nativeEvent.error) }; onError === null || onError === void 0 || onError(event); } : onError, onLoadEnd: onLoadEnd && (() => { var _fallbackResult$curre; return onLoadEnd((_fallbackResult$curre = fallbackResult.current) !== null && _fallbackResult$curre !== void 0 ? _fallbackResult$curre : { ok: false, error: "Failed to load the image" }); }), resizeMode }), children); } const sources = Array.isArray(source) ? source.length === 1 ? void 0 : source : void 0; const single = Array.isArray(source) ? source[0] : source; const resolvedSource = sources ? void 0 : Image.resolveAssetSource(single); const resolvedDefaultSource = resolveDefaultSource(defaultSource); return /* @__PURE__ */ React.createElement(View, { style: [styles.imageContainer, style], ...wrapperProps, ref: forwardedRef }, /* @__PURE__ */ React.createElement(FastImageView, { ...imageProps, tintColor: resolvedTintColor, loopCount: loopCount(loop), ...transitionProps(transition), style: StyleSheet.absoluteFill, source: resolvedSource, sources, defaultSource: resolvedDefaultSource, onFastImageLoadStart: onLoadStart, onFastImageProgress: withProgress(onProgress), trackProgress: !!onProgress, onFastImageLoad: onLoad, onFastImageError: onError, onFastImageLoadEnd: onLoadEnd && ((event) => onLoadEnd(loadResult(event.nativeEvent))), resizeMode }), children); } const FastImageMemo = memo(FastImageBase); const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.createElement(FastImageMemo, { forwardedRef: ref, ...props })); FastImageComponent.displayName = "FastImage"; const noResult = { ok: false, error: "No result" }; const FastImage = FastImageComponent; FastImage.resizeMode = resizeMode; FastImage.cacheControl = cacheControl; FastImage.priority = priority; const ONE_SOURCE = "Takes one source, not an array of sizes: pass the size to use"; FastImage.preload = (sources) => Promise.resolve(NativeModules.FastImageView.preload(sources.map((s) => s && !Array.isArray(s) ? s : {}))).then((results) => sources.map((source, i) => { var _results$i; if (Array.isArray(source)) return { ok: false, error: ONE_SOURCE, uri: void 0 }; const uri = source ? source.uri : void 0; const result = (_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : noResult; if (!result.ok) return { ...result, uri }; return typeof uri === "string" ? { ...result, uri } : { ok: false, error: "Invalid source: no uri", uri }; })); FastImage.clearMemoryCache = () => Promise.resolve(NativeModules.FastImageView.clearMemoryCache()); FastImage.clearDiskCache = () => Promise.resolve(NativeModules.FastImageView.clearDiskCache()); FastImage.configureCache = (limits = {}) => Promise.resolve(NativeModules.FastImageView.configureCache(limits)); FastImage.writeToCache = (source, file) => Array.isArray(source) ? Promise.resolve({ ok: false, error: ONE_SOURCE }) : Promise.resolve(NativeModules.FastImageView.writeToCache(source || {}, file)); FastImage.getCachePath = (source) => Array.isArray(source) ? Promise.resolve({ ok: false, error: ONE_SOURCE }) : Promise.resolve(NativeModules.FastImageView.getCachePath(source || {})); const styles = StyleSheet.create({ fallbackImage: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }, imageContainer: { overflow: "hidden" } }); /** * An image with content on top of it, like React Native's `ImageBackground`: a * view that the image fills, with the children on top. Use it rather than * giving `FastImage` children, which it won't render in the next major version * (the image will be a single native view). The other props go to the image; * the ref is the view's. */ const FastImageBackground = forwardRef(({ style, imageStyle, imageRef, children, importantForAccessibility, ...props }, ref) => /* @__PURE__ */ React.createElement(View, { accessibilityIgnoresInvertColors: true, importantForAccessibility, style, ref }, /* @__PURE__ */ React.createElement(FastImageComponent, { ...props, importantForAccessibility, style: [StyleSheet.absoluteFill, imageStyle], ref: imageRef }), children)); FastImageBackground.displayName = "FastImageBackground"; const FastImageView = requireNativeComponent("FastImageView", FastImage, { nativeOnly: { onFastImageLoadStart: true, onFastImageProgress: true, onFastImageLoad: true, onFastImageError: true, onFastImageLoadEnd: true, trackProgress: true } }); //#endregion export { FastImageBackground, FastImage as default };