UNPKG

@tamagui/react-native-web-lite

Version:
240 lines (239 loc) 8.58 kB
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"; import PixelRatio from "../PixelRatio/index"; import View from "../View/index"; import { jsx, jsxs } from "react/jsx-runtime"; const ERRORED = "ERRORED", LOADED = "LOADED", LOADING = "LOADING", 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: !0, 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), { filter, resizeMode, shadowOffset, tintColor } = flatStyle, filters = []; let _filter = null; if (filter && filters.push(filter), blurRadius && filters.push(`blur(${blurRadius}px)`), shadowOffset) { const shadowString = createBoxShadowValue(flatStyle); shadowString && filters.push(`drop-shadow(${shadowString})`); } return tintColor && filterId != null && filters.push(`url(#tint-${filterId})`), 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, [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 typeof source == "string" ? uri = source : source && typeof source.uri == "string" && (uri = source.uri); if (uri) { const match = uri.match(svgDataUriPattern); if (match) { const [, prefix, svg] = match, 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" && 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); return uri2 != null && ImageLoader.has(uri2) ? LOADED : IDLE; }), [layout, updateLayout] = React.useState({}), hasTextAncestor = React.useContext(TextAncestorContext), hiddenImageRef = React.useRef(null), filterRef = React.useRef(_filterId++), requestRef = React.useRef(null), shouldDisplaySource = state === LOADED || state === LOADING && defaultSource == null, [flatStyle, _resizeMode, filter, tintColor] = getFlatStyle( {}, blurRadius, filterRef.current ), resizeMode = props.resizeMode || _resizeMode || "cover", selectedSource = shouldDisplaySource ? source : defaultSource, displayImageUri = resolveAssetUri(selectedSource), imageSizeStyle = resolveAssetDimensions(selectedSource), backgroundImage = displayImageUri ? `url("${displayImageUri}")` : null, backgroundSize = getBackgroundSize(), hiddenImage = displayImageUri ? createElement("img", { alt: accessibilityLabel || "", style: styles.accessibilityImage$raw, draggable: draggable || !1, ref: hiddenImageRef, src: displayImageUri }) : null; function getBackgroundSize() { if (hiddenImageRef.current != null && (resizeMode === "center" || resizeMode === "repeat")) { const { naturalHeight, naturalWidth } = hiddenImageRef.current, { height, width } = layout; if (naturalHeight && naturalWidth && height && width) { const scaleFactor = Math.min(1, width / naturalWidth, height / naturalHeight), x = Math.ceil(scaleFactor * naturalWidth), 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); return React.useEffect(() => { abortPendingRequest(), uri != null && (updateState(LOADING), onLoadStart && onLoadStart(), requestRef.current = ImageLoader.load( uri, function(e) { updateState(LOADED), onLoad && onLoad(e), onLoadEnd && onLoadEnd(); }, function() { updateState(ERRORED), onError && onError({ nativeEvent: { error: `Failed to load resource ${uri} (404)` } }), onLoadEnd && onLoadEnd(); } )); function abortPendingRequest() { requestRef.current != null && (ImageLoader.abort(requestRef.current), requestRef.current = null); } return abortPendingRequest; }, [uri, requestRef, updateState, onError, onLoad, onLoadEnd, onLoadStart]), /* @__PURE__ */ jsxs( View, { ...rest, 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: !0 } ), 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 } }), 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 { Image_default as default }; //# sourceMappingURL=index.js.map