react-native-fast-image
Version:
FastImage, performant React Native image component.
451 lines (450 loc) • 16.1 kB
JavaScript
Object.defineProperties(exports, {
__esModule: { value: true },
[Symbol.toStringTag]: { value: "Module" }
});
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let react = require("react");
react = __toESM(react);
let react_native = require("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/objectFit.ts
function fromStyle(style, key) {
if (Array.isArray(style)) {
for (let i = style.length - 1; i >= 0; i--) {
const found = fromStyle(style[i], key);
if (found !== void 0) return found;
}
return;
}
if (typeof style === "number") {
const flattened = react_native.StyleSheet.flatten(style);
return flattened ? flattened[key] : void 0;
}
return style && typeof style === "object" ? style[key] : void 0;
}
const FITS = {
fill: true,
contain: true,
cover: true,
none: true,
"scale-down": true
};
function resolveObjectFit(objectFit, style) {
const fit = objectFit != null ? objectFit : fromStyle(style, "objectFit");
return fit && FITS[fit] === true ? fit : "cover";
}
//#endregion
//#region src/warnings.ts
const warned = /* @__PURE__ */ new Set();
function warnOnce(key, message, level) {
if (!warned.has(key)) {
warned.add(key);
console[level](message);
}
}
function warnIfChildren(children) {
if (react.Children.toArray(children).length > 0) warnOnce("children", "react-native-fast-image: FastImage doesn't render children (since 10.0): use FastImageBackground for content over an image.", "error");
}
//#endregion
//#region src/objectPosition.ts
const START = {
fraction: 0,
offset: 0
};
const CENTER = {
fraction: .5,
offset: 0
};
const END = {
fraction: 1,
offset: 0
};
/** The default, `50% 50%`. */
const CENTERED = {
x: CENTER,
y: CENTER
};
const NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
function length(text) {
const lower = text.toLowerCase();
if (lower.endsWith("%")) {
const number = lower.slice(0, -1);
return NUMBER.test(number) ? {
fraction: Number(number) / 100,
offset: 0
} : null;
}
if (lower.endsWith("px")) {
const number = lower.slice(0, -2);
return NUMBER.test(number) ? {
fraction: 0,
offset: Number(number)
} : null;
}
return NUMBER.test(lower) && Number(lower) === 0 ? START : null;
}
function token(text) {
const lower = text.toLowerCase();
if (lower === "left" || lower === "right" || lower === "top" || lower === "bottom" || lower === "center") return { keyword: lower };
const axis = length(text);
return axis ? { length: axis } : null;
}
const X = {
left: START,
center: CENTER,
right: END
};
const Y = {
top: START,
center: CENTER,
bottom: END
};
const x = (t) => "length" in t ? t.length : X[t.keyword];
const y = (t) => "length" in t ? t.length : Y[t.keyword];
function fromEdge(edge, axis) {
return edge === "left" || edge === "top" ? axis : {
fraction: 1 - axis.fraction,
offset: -axis.offset || 0
};
}
function parse(text) {
const tokens = [];
for (const part of text.trim().split(/\s+/)) {
const t = token(part);
if (!t) return null;
tokens.push(t);
}
if (tokens.length === 1) {
const [a] = tokens;
if ("keyword" in a && (a.keyword === "top" || a.keyword === "bottom")) return {
x: CENTER,
y: Y[a.keyword]
};
return {
x: x(a),
y: CENTER
};
}
if (tokens.length === 2) {
const [a, b] = tokens;
const ax = x(a);
const by = y(b);
if (ax && by) return {
x: ax,
y: by
};
if ("keyword" in a && "keyword" in b) {
const bx = X[b.keyword];
const ay = Y[a.keyword];
if (bx && ay) return {
x: bx,
y: ay
};
}
return null;
}
if (tokens.length === 4) {
const [a, b, c, d] = tokens;
if (!("keyword" in a && "length" in b && "keyword" in c && "length" in d)) return null;
const horizontal = (k) => k === "left" || k === "right";
const vertical = (k) => k === "top" || k === "bottom";
if (horizontal(a.keyword) && vertical(c.keyword)) return {
x: fromEdge(a.keyword, b.length),
y: fromEdge(c.keyword, d.length)
};
if (vertical(a.keyword) && horizontal(c.keyword)) return {
x: fromEdge(c.keyword, d.length),
y: fromEdge(a.keyword, b.length)
};
}
return null;
}
const parsed = /* @__PURE__ */ new Map();
function objectLength(value) {
if (typeof value === "number") return Number.isFinite(value) ? {
fraction: 0,
offset: value
} : null;
return typeof value === "string" && value.endsWith("%") ? length(value) : null;
}
/**
* The position for objectPosition, or null if it isn't valid. A string is
* CSS's syntax; an object has one of `top` and `bottom` and one of `left`
* and `right`, each in points or a percentage, and a missing axis is
* centered. Unset is centered.
*/
function resolveObjectPosition(position) {
if (position == null) return CENTERED;
if (typeof position === "string") {
let result = parsed.get(position);
if (result === void 0) {
result = parse(position);
if (parsed.size < 100) parsed.set(position, result);
}
return result;
}
if (typeof position !== "object") return null;
const edges = position;
const axis = (start, end) => {
const hasStart = edges[start] != null;
const hasEnd = edges[end] != null;
if (hasStart && hasEnd) return null;
if (!hasStart && !hasEnd) return CENTER;
const edge = hasStart ? start : end;
const value = objectLength(edges[edge]);
return value && fromEdge(edge, value);
};
const resolvedX = axis("left", "right");
const resolvedY = axis("top", "bottom");
return resolvedX && resolvedY ? {
x: resolvedX,
y: resolvedY
} : null;
}
/**
* The position for objectPosition, or centered if it isn't valid (as CSS
* ignores it), with a warning in development.
*/
function objectPositionOrCentered(position) {
const resolved = resolveObjectPosition(position);
if (resolved) return resolved;
if (typeof __DEV__ !== "undefined" && __DEV__) {
const value = JSON.stringify(position);
warnOnce(`objectPosition ${value}`, `react-native-fast-image: objectPosition ${value} isn't a position, so the image is centered. Use CSS's syntax with px or % (e.g. 'right 10px bottom 20%'), or { top | bottom, left | right } in points or percentages.`, "warn");
}
return CENTERED;
}
//#endregion
//#region src/specs/FastImageViewNativeComponent.ts
var FastImageViewNativeComponent_default = (0, react_native.codegenNativeComponent)("FastImageView");
//#endregion
//#region src/specs/NativeFastImageModule.ts
var NativeFastImageModule_default = react_native.TurboModuleRegistry.get("FastImageModule");
//#endregion
//#region src/index.tsx
const resolveDefaultSource = (defaultSource) => {
if (!defaultSource) return null;
const resolved = react_native.Image.resolveAssetSource(defaultSource);
if (!resolved) return null;
return react_native.Platform.OS === "android" ? resolved.uri : resolved;
};
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 handledEvents({ onLoadStart, onProgress, onLoad, onError, onLoadEnd }) {
return (onLoadStart ? 1 : 0) | (onProgress ? 2 : 0) | (onLoad ? 4 : 0) | (onError ? 8 : 0) | (onLoadEnd ? 16 : 0);
}
function usualFadeMs() {
return react_native.Platform.OS === "ios" ? 250 : 300;
}
const NO_TRANSITION = {
transitionDuration: 0,
transitionBetweenImages: false,
transitionSkipOnCacheHit: "memory"
};
function resolveTransition(transition, base) {
if (transition == null) return base;
if (transition === false) return {
...base,
transitionDuration: 0
};
const usual = base.transitionDuration > 0 ? base.transitionDuration : usualFadeMs();
if (transition === true) return {
...base,
transitionDuration: usual
};
const { duration, betweenImages, skipOnCacheHit } = typeof transition === "number" ? { duration: transition } : transition;
const resolvedDuration = duration !== null && duration !== void 0 ? duration : usual;
return {
transitionDuration: typeof resolvedDuration === "number" && resolvedDuration > 0 ? resolvedDuration : 0,
transitionBetweenImages: betweenImages !== null && betweenImages !== void 0 ? betweenImages : base.transitionBetweenImages,
transitionSkipOnCacheHit: skipOnCacheHit !== null && skipOnCacheHit !== void 0 ? skipOnCacheHit : base.transitionSkipOnCacheHit
};
}
function builtInTransition() {
return resolveTransition(react_native.Platform.OS === "android", NO_TRANSITION);
}
let defaultTransition;
function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, forwardedRef, ...viewProps }) {
var _props$recyclingKey, _defaultTransition;
const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
if (__DEV__ && fallback !== void 0) warnOnce("fallback", "react-native-fast-image: `fallback` was removed in 10.0, and does nothing: images always load natively. Remove it, or use React Native's Image for those images.", "warn");
if (__DEV__) warnIfChildren(children);
const resolvedTintColor = tintColor != null ? tintColor : fromStyle(style, "tintColor");
const fit = resolveObjectFit(objectFit, style);
const position = objectPositionOrCentered(objectPosition);
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 : react_native.Image.resolveAssetSource(single);
const resolvedDefaultSource = resolveDefaultSource(defaultSource);
return /* @__PURE__ */ react.default.createElement(FastImageViewNativeComponent_default, {
...props,
ref: forwardedRef,
style: [styles.image, style],
recyclingKey: (_props$recyclingKey = props.recyclingKey) !== null && _props$recyclingKey !== void 0 ? _props$recyclingKey : void 0,
loopCount: loopCount(loop),
...resolveTransition(transition, (_defaultTransition = defaultTransition) !== null && _defaultTransition !== void 0 ? _defaultTransition : builtInTransition()),
source: resolvedSource,
sources,
defaultSource: resolvedDefaultSource,
onFastImageLoadStart: onLoadStart,
onFastImageProgress: onProgress,
handledEvents: handledEvents({
onLoadStart,
onProgress,
onLoad,
onError,
onLoadEnd
}),
onFastImageLoad: onLoad,
onFastImageError: onError,
onFastImageLoadEnd: onLoadEnd && ((event) => onLoadEnd(loadResult(event.nativeEvent))),
tintColor: resolvedTintColor,
objectFit: fit,
objectPositionX: position.x.fraction,
objectPositionXOffset: position.x.offset,
objectPositionY: position.y.fraction,
objectPositionYOffset: position.y.offset
});
}
const FastImageMemo = (0, react.memo)(FastImageBase);
const FastImageComponent = (0, react.forwardRef)((props, ref) => /* @__PURE__ */ react.default.createElement(FastImageMemo, {
forwardedRef: ref,
...props
}));
FastImageComponent.displayName = "FastImage";
const noResult = {
ok: false,
error: "No result"
};
function nativeModule() {
if (!NativeFastImageModule_default) throw new Error("react-native-fast-image: the native module FastImageModule isn't in this app. Rebuild the app after installing the package (run pod install on iOS). It needs the New Architecture, and isn't in Expo Go: use a development build. In Jest, mock the function (e.g. jest.spyOn(FastImage, \"preload\")).");
return NativeFastImageModule_default;
}
const FastImage = FastImageComponent;
FastImage.cacheControl = cacheControl;
FastImage.priority = priority;
FastImage.resizeMode = resizeMode;
FastImage.setDefaults = ({ transition } = {}) => {
if (transition === null) defaultTransition = void 0;
else if (transition !== void 0) defaultTransition = resolveTransition(transition, builtInTransition());
};
const ONE_SOURCE = "Takes one source, not an array of sizes: pass the size to use";
const isSourceObject = (source) => typeof source === "object" && source !== null && !Array.isArray(source);
FastImage.preload = (sources) => Promise.resolve(nativeModule().preload(sources.map((s) => isSourceObject(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 = isSourceObject(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 = () => nativeModule().clearMemoryCache();
FastImage.clearDiskCache = () => nativeModule().clearDiskCache();
FastImage.configureCache = (limits) => {
const set = {};
const reset = [];
for (const [name, value] of Object.entries(limits !== null && limits !== void 0 ? limits : {})) if (value === null) reset.push(name);
else if (typeof value === "number") set[name] = value;
return nativeModule().configureCache({
...set,
reset
});
};
FastImage.writeToCache = (source, file) => Array.isArray(source) ? Promise.resolve({
ok: false,
error: ONE_SOURCE
}) : Promise.resolve(nativeModule().writeToCache(isSourceObject(source) ? source : {}, file));
FastImage.getCachePath = (source) => Array.isArray(source) ? Promise.resolve({
ok: false,
error: ONE_SOURCE
}) : Promise.resolve(nativeModule().getCachePath(isSourceObject(source) ? source : {}));
const styles = react_native.StyleSheet.create({ image: { 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 (`FastImage` is a single
* native view, which doesn't take children). The other props go to the image;
* the ref is the view's.
*/
const FastImageBackground = (0, react.forwardRef)(({ style, imageStyle, imageRef, children, importantForAccessibility, ...props }, ref) => /* @__PURE__ */ react.default.createElement(react_native.View, {
accessibilityIgnoresInvertColors: true,
importantForAccessibility,
style,
ref
}, /* @__PURE__ */ react.default.createElement(FastImageComponent, {
...props,
importantForAccessibility,
style: [react_native.StyleSheet.absoluteFill, imageStyle],
ref: imageRef
}), children));
FastImageBackground.displayName = "FastImageBackground";
//#endregion
exports.FastImageBackground = FastImageBackground;
exports.default = FastImage;