@tamagui/react-native-web-lite
Version:
React Native for Web
319 lines (318 loc) • 9.21 kB
JavaScript
import * as React from "react";
import { StyleSheet, TextAncestorContext, createBoxShadowValue } from "@tamagui/react-native-web-internals";
import { ImageLoader, getAssetByID } from "@tamagui/react-native-web-internals";
import { createElement } from "../createElement/index.mjs";
import { PixelRatio } from "../PixelRatio/index.mjs";
import { View } from "../View/index.mjs";
import { jsx, jsxs } from "react/jsx-runtime";
const ERRORED = "ERRORED";
const LOADED = "LOADED";
const LOADING = "LOADING";
const IDLE = "IDLE";
let _filterId = 0;
const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/;
function createTintColorSVG(tintColor, id) {
return tintColor && id != null ? /* @__PURE__ */jsx("svg", {
style: {
position: "absolute",
height: 0,
visibility: "hidden",
width: 0
},
children: /* @__PURE__ */jsx("defs", {
children: /* @__PURE__ */jsxs("filter", {
id: `tint-${id}`,
suppressHydrationWarning: true,
children: [/* @__PURE__ */jsx("feFlood", {
floodColor: `${tintColor}`
}, tintColor), /* @__PURE__ */jsx("feComposite", {
in2: "SourceAlpha",
operator: "atop"
})]
})
})
}) : null;
}
function getFlatStyle(style, blurRadius, filterId) {
const flatStyle = StyleSheet.flatten(style);
const {
filter,
resizeMode,
shadowOffset,
tintColor
} = flatStyle;
const filters = [];
let _filter = null;
if (filter) {
filters.push(filter);
}
if (blurRadius) {
filters.push(`blur(${blurRadius}px)`);
}
if (shadowOffset) {
const shadowString = createBoxShadowValue(flatStyle);
if (shadowString) {
filters.push(`drop-shadow(${shadowString})`);
}
}
if (tintColor && filterId != null) {
filters.push(`url(#tint-${filterId})`);
}
if (filters.length > 0) {
_filter = filters.join(" ");
}
delete flatStyle.blurRadius;
delete flatStyle.shadowColor;
delete flatStyle.shadowOpacity;
delete flatStyle.shadowOffset;
delete flatStyle.shadowRadius;
delete flatStyle.tintColor;
delete flatStyle.overlayColor;
delete flatStyle.resizeMode;
return [flatStyle, resizeMode, _filter, tintColor];
}
function resolveAssetDimensions(source) {
if (typeof source === "number") {
const {
height,
width
} = getAssetByID(source);
return {
height,
width
};
} else if (source != null && !Array.isArray(source) && typeof source === "object") {
const {
height,
width
} = source;
return {
height,
width
};
}
}
function resolveAssetUri(source) {
let uri = null;
if (typeof source === "number") {
const asset = getAssetByID(source);
let scale = asset.scales[0];
if (asset.scales.length > 1) {
const preferredScale = PixelRatio.get();
scale = asset.scales.reduce((prev, curr) => Math.abs(curr - preferredScale) < Math.abs(prev - preferredScale) ? curr : prev);
}
const scaleSuffix = scale !== 1 ? `@${scale}x` : "";
uri = asset ? `${asset.httpServerLocation}/${asset.name}${scaleSuffix}.${asset.type}` : "";
} else if (typeof source === "string") {
uri = source;
} else if (source && typeof source.uri === "string") {
uri = source.uri;
}
if (uri) {
const match = uri.match(svgDataUriPattern);
if (match) {
const [, prefix, svg] = match;
const encodedSvg = encodeURIComponent(svg);
return `${prefix}${encodedSvg}`;
}
}
return uri;
}
const Image = React.forwardRef((props, ref) => {
const {
accessibilityLabel,
blurRadius,
defaultSource,
draggable,
onError,
onLayout,
onLoad,
onLoadEnd,
onLoadStart,
pointerEvents,
source,
style,
...rest
} = props;
if (process.env.NODE_ENV !== "production") {
if (props.children) {
throw new Error("The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.");
}
}
const [state, updateState] = React.useState(() => {
const uri2 = resolveAssetUri(source);
if (uri2 != null) {
const isLoaded = ImageLoader.has(uri2);
if (isLoaded) {
return LOADED;
}
}
return IDLE;
});
const [layout, updateLayout] = React.useState({});
const hasTextAncestor = React.useContext(TextAncestorContext);
const hiddenImageRef = React.useRef(null);
const filterRef = React.useRef(_filterId++);
const requestRef = React.useRef(null);
const shouldDisplaySource = state === LOADED || state === LOADING && defaultSource == null;
const [flatStyle, _resizeMode, filter, tintColor] = getFlatStyle({}, blurRadius, filterRef.current);
const resizeMode = props.resizeMode || _resizeMode || "cover";
const selectedSource = shouldDisplaySource ? source : defaultSource;
const displayImageUri = resolveAssetUri(selectedSource);
const imageSizeStyle = resolveAssetDimensions(selectedSource);
const backgroundImage = displayImageUri ? `url("${displayImageUri}")` : null;
const backgroundSize = getBackgroundSize();
const hiddenImage = displayImageUri ? createElement("img", {
alt: accessibilityLabel || "",
style: styles.accessibilityImage$raw,
draggable: draggable || false,
ref: hiddenImageRef,
src: displayImageUri
}) : null;
function getBackgroundSize() {
if (hiddenImageRef.current != null && (resizeMode === "center" || resizeMode === "repeat")) {
const {
naturalHeight,
naturalWidth
} = hiddenImageRef.current;
const {
height,
width
} = layout;
if (naturalHeight && naturalWidth && height && width) {
const scaleFactor = Math.min(1, width / naturalWidth, height / naturalHeight);
const x = Math.ceil(scaleFactor * naturalWidth);
const y = Math.ceil(scaleFactor * naturalHeight);
return `${x}px ${y}px`;
}
}
}
function handleLayout(e) {
if (resizeMode === "center" || resizeMode === "repeat" || onLayout) {
const {
layout: layout2
} = e.nativeEvent;
onLayout && onLayout(e);
updateLayout(layout2);
}
}
const uri = resolveAssetUri(source);
React.useEffect(() => {
abortPendingRequest();
if (uri != null) {
updateState(LOADING);
if (onLoadStart) {
onLoadStart();
}
requestRef.current = ImageLoader.load(uri, function load(e) {
updateState(LOADED);
if (onLoad) {
onLoad(e);
}
if (onLoadEnd) {
onLoadEnd();
}
}, function error() {
updateState(ERRORED);
if (onError) {
onError({
nativeEvent: {
error: `Failed to load resource ${uri} (404)`
}
});
}
if (onLoadEnd) {
onLoadEnd();
}
});
}
function abortPendingRequest() {
if (requestRef.current != null) {
ImageLoader.abort(requestRef.current);
requestRef.current = null;
}
}
return abortPendingRequest;
}, [uri, requestRef, updateState, onError, onLoad, onLoadEnd, onLoadStart]);
return /* @__PURE__ */jsxs(View, {
...rest,
"aria-label": accessibilityLabel,
onLayout: handleLayout,
pointerEvents,
ref,
style: [style, styles.root, hasTextAncestor && styles.inline, imageSizeStyle, flatStyle],
children: [/* @__PURE__ */jsx(View, {
style: [...[].concat(styles.image), resizeModeStyles[resizeMode], {
backgroundImage,
filter
}, backgroundSize != null && {
backgroundSize
}],
suppressHydrationWarning: true
}), hiddenImage, createTintColorSVG(tintColor, filterRef.current)]
});
});
Image.displayName = "Image";
const ImageWithStatics = Image;
ImageWithStatics.getSize = function (uri, success, failure) {
ImageLoader.getSize(uri, success, failure);
};
ImageWithStatics.prefetch = function (uri) {
return ImageLoader.prefetch(uri);
};
ImageWithStatics.queryCache = function (uris) {
return ImageLoader.queryCache(uris);
};
const styles = StyleSheet.create({
root: {
flexBasis: "auto",
overflow: "hidden",
zIndex: 0
},
inline: {
display: "inline-flex"
},
image: {
...StyleSheet.absoluteFillObject,
backgroundColor: "transparent",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "cover",
height: "100%",
width: "100%",
zIndex: -1
},
accessibilityImage$raw: {
...StyleSheet.absoluteFillObject,
height: "100%",
opacity: 0,
width: "100%",
zIndex: -1
}
});
const resizeModeStyles = StyleSheet.create({
center: {
backgroundSize: "auto"
},
contain: {
backgroundSize: "contain"
},
cover: {
backgroundSize: "cover"
},
none: {
backgroundPosition: "0",
backgroundSize: "auto"
},
repeat: {
backgroundPosition: "0",
backgroundRepeat: "repeat",
backgroundSize: "auto"
},
stretch: {
backgroundSize: "100% 100%"
}
});
var Image_default = ImageWithStatics;
export { ImageWithStatics as Image, Image_default as default };
//# sourceMappingURL=index.mjs.map