UNPKG

@tamagui/react-native-web-lite

Version:
319 lines (318 loc) 9.21 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.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