/** * ✔ hover-class * ✘ hover-stop-propagation * ✔ hover-start-time * ✔ hover-stay-time */ import { View, TextStyle, NativeSyntheticEvent, ViewProps, ImageStyle, StyleSheet, Image, LayoutChangeEvent } from 'react-native' import { useRef, useState, useEffect, forwardRef, ReactNode, JSX, createElement } from 'react' import useInnerProps from './getInnerListeners' import Animated from 'react-native-reanimated' import useAnimationHooks, { AnimationType } from './animationHooks/index' import type { AnimationProp } from './animationHooks/utils' import { ExtendedViewStyle } from './types/common' import useNodesRef, { HandlerRef } from './useNodesRef' import { parseUrl, PERCENT_REGEX, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout, renderImage, pickStyle, extendObject, useHover } from './utils' import { error, isFunction } from '@mpxjs/utils' import LinearGradient from 'react-native-linear-gradient' import { GestureDetector, PanGesture } from 'react-native-gesture-handler' import Portal from './mpx-portal' export interface _ViewProps extends ViewProps { style?: ExtendedViewStyle animation?: AnimationProp children?: ReactNode | ReactNode[] 'hover-style'?: ExtendedViewStyle 'hover-start-time'?: number 'hover-stay-time'?: number 'enable-background'?: boolean 'enable-var'?: boolean 'enable-fast-image'?: boolean 'external-var-context'?: Record 'parent-font-size'?: number 'parent-width'?: number 'parent-height'?: number 'enable-animation'?: boolean | AnimationType bindtouchstart?: (event: NativeSyntheticEvent | unknown) => void bindtouchmove?: (event: NativeSyntheticEvent | unknown) => void bindtouchend?: (event: NativeSyntheticEvent | unknown) => void bindtransitionend?: (event: NativeSyntheticEvent | unknown) => void catchtransitionend?: (event: NativeSyntheticEvent | unknown) => void } type Handler = (...args: any[]) => void type Size = { width: number height: number } type DimensionValue = number | `${number}%` | 'auto' | 'contain' | 'cover' type Position = { left?: number right?: number top?: number bottom?: number } type PositionKey = keyof Position type NumberVal = number | `${number}%` type PositionVal = PositionKey | NumberVal type backgroundPositionList = ['left' | 'right', NumberVal, 'top' | 'bottom', NumberVal] | [] type LinearInfo = { colors: Array, locations: Array, direction?: string } type PreImageInfo = { src?: string, sizeList: DimensionValue[] type?: 'image' | 'linear' linearInfo?: LinearInfo // containPercentSymbol?: boolean backgroundPosition: backgroundPositionList } type ImageProps = { style: ImageStyle, src?: string, source?: { uri: string }, colors: Array, locations?: Array angle?: number resizeMode?: 'cover' | 'stretch' } const linearMap = new Map([ ['top', 0], ['bottom', 180], ['left', 270], ['right', 90] ]) // 对角线角度 const diagonalAngleMap: Record any> = { 'top right': (width: number, height: number) => { return Math.acos( (width / 2) / (Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) / 2) ) }, 'right top': (width, height) => { return diagonalAngleMap['top right'](width, height) }, 'bottom right': (width, height) => Math.PI - diagonalAngleMap['top right'](width, height), 'right bottom': (width, height) => { return diagonalAngleMap['bottom right'](width, height) }, 'bottom left': (width, height) => Math.PI + diagonalAngleMap['top right'](width, height), 'left bottom': (width, height) => { return diagonalAngleMap['bottom left'](width, height) }, 'top left': (width, height) => (2 * Math.PI) - diagonalAngleMap['top right'](width, height), 'left top': (width, height) => { return diagonalAngleMap['top left'](width, height) } } // 弧度转化为角度的公式 function radToAngle (r: number) { return r * 180 / Math.PI } const applyHandlers = (handlers: Handler[], args: any[]) => { for (const handler of handlers) { handler(...args) } } const normalizeStyle = (style: ExtendedViewStyle = {}) => { ['backgroundSize', 'backgroundPosition'].forEach(name => { if (style[name] && typeof style[name] === 'string') { if (style[name].trim()) { style[name] = style[name].split(' ') } } }) return style } const isPercent = (val: string | number | undefined): val is string => typeof val === 'string' && PERCENT_REGEX.test(val) const isBackgroundSizeKeyword = (val: string | number): boolean => typeof val === 'string' && /^cover|contain$/.test(val) const isNeedLayout = (preImageInfo: PreImageInfo): boolean => { const { sizeList, backgroundPosition, linearInfo, type } = preImageInfo const [width, height] = sizeList const bp = backgroundPosition // 含有百分号,center 需计算布局 return isBackgroundSizeKeyword(width) || (isPercent(height) && width === 'auto') || (isPercent(width) && height === 'auto') || isPercent(bp[1]) || isPercent(bp[3]) || isDiagonalAngle(linearInfo) || (type === 'linear' && (isPercent(height) || isPercent(width))) } const checkNeedLayout = (preImageInfo: PreImageInfo) => { const { sizeList } = preImageInfo const [width] = sizeList // 在渐变的时候,background-size的cover,contain, auto属性值,转化为100%, needLayout计算逻辑和原来保持一致,needImageSize始终为false return { // 是否开启layout的计算 needLayout: isNeedLayout(preImageInfo), // 是否开启原始宽度的计算 needImageSize: isBackgroundSizeKeyword(width) || sizeList.includes('auto') } } /** * h - 用户设置的高度 * lh - 容器的高度 * ratio - 原始图片的宽高比 * **/ function calculateSize (h: number, ratio: number, lh?: number | boolean, reverse = false): Size | null { let height = 0; let width = 0 if (typeof lh === 'boolean') { reverse = lh } if (isPercent(h)) { // auto px/rpx if (!lh) return null height = (parseFloat(h) / 100) * (lh as number) width = height * ratio } else { // 2. auto px/rpx - 根据比例计算 height = h width = height * ratio } return { width: reverse ? height : width, height: reverse ? width : height } } /** * 用户设置百分比后,转换为偏移量 * h - 用户设置图片的高度 * ch - 容器的高度 * val - 用户设置的百分比 * **/ function calculateSizePosition (h: number, ch: number, val: string): number { if (!h || !ch) return 0 // 百分比需要单独的计算 if (isPercent(h)) { h = ch * parseFloat(h) / 100 } // (container width - image width) * (position x%) = (x offset value) return (ch - h) * parseFloat(val) / 100 } /** * 获取图片的展示宽高 * h - 用户设置的高度 * lh - 容器的高度 * **/ const calcPercent = (h: NumberVal, lh: number) => { return isPercent(h) ? parseFloat(h) / 100 * lh : +h } function backgroundPosition (imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) { const bps = preImageInfo.backgroundPosition if (bps.length === 0) return const style: Position = {} const imageStyle: ImageStyle = imageProps.style || {} for (let i = 0; i < bps.length; i += 2) { const key = bps[i] as PositionKey; const val = bps[i + 1] // 需要获取 图片宽度 和 容器的宽度 进行计算 if (isPercent(val)) { if (i === 0) { style[key] = calculateSizePosition(imageStyle.width as number, layoutInfo?.width, val) } else { style[key] = calculateSizePosition(imageStyle.height as number, layoutInfo?.height, val) } } else { style[key] = val as number } } extendObject(imageProps.style, style) } // background-size 转换 function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) { const { sizeList, type } = preImageInfo if (!sizeList) return const { width: layoutWidth, height: layoutHeight } = layoutInfo || {} const { width: imageSizeWidth, height: imageSizeHeight } = imageSize || {} const [width, height] = sizeList let dimensions: { width: NumberVal, height: NumberVal } | null = { width: 0, height: 0 } // 枚举值 if (typeof width === 'string' && ['cover', 'contain'].includes(width)) { if (layoutInfo && imageSize) { const layoutRatio = layoutWidth / imageSizeWidth const eleRatio = imageSizeWidth / imageSizeHeight // 容器宽高比 大于 图片的宽高比,依据宽度作为基准,否则以高度为基准 if ((layoutRatio <= eleRatio && (width as string) === 'contain') || (layoutRatio >= eleRatio && (width as string) === 'cover')) { dimensions = calculateSize(layoutWidth as number, imageSizeHeight / imageSizeWidth, true) as Size } else if ((layoutRatio > eleRatio && (width as string) === 'contain') || (layoutRatio < eleRatio && (width as string) === 'cover')) { dimensions = calculateSize(layoutHeight as number, imageSizeWidth / imageSizeHeight) as Size } } } else { if (width === 'auto' && height === 'auto') { // 均为auto if (!imageSize) return dimensions = { width: imageSizeWidth, height: imageSizeHeight } } else if (width === 'auto') { // auto px/rpx/% if (!imageSize) return dimensions = calculateSize(height as number, imageSizeWidth / imageSizeHeight, layoutInfo?.height) if (!dimensions) return } else if (height === 'auto') { // auto px/rpx/% if (!imageSize) return dimensions = calculateSize(width as number, imageSizeHeight / imageSizeWidth, layoutInfo?.width, true) if (!dimensions) return } else { // 数值类型 ImageStyle // 数值类型设置为 stretch imageProps.resizeMode = 'stretch' if (type === 'linear' && (!layoutWidth || !layoutHeight) && (isPercent(width) || isPercent(height))) { // ios 上 linear 组件只要重新触发渲染,在渲染过程中外层容器 width 或者 height 被设置为 0,通过设置 % 的方式会渲染不出来,即使后面再更新为正常宽高也渲染不出来 // 所以 hack 手动先将 linear 宽高也设置为 0,后面再更新为正确的数值或 %。 dimensions = { width: 0, height: 0 } as { width: NumberVal, height: NumberVal } } else { dimensions = { width: isPercent(width) ? width : +width, height: isPercent(height) ? height : +height } as { width: NumberVal, height: NumberVal } } } } // 样式合并 extendObject(imageProps.style, dimensions) } // background-image转换为source function backgroundImage (imageProps: ImageProps, preImageInfo: PreImageInfo) { const src = preImageInfo.src if (src) { imageProps.source = { uri: src } } } // 渐变的转换 function linearGradient (imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) { const { type, linearInfo } = preImageInfo const { colors = [], locations, direction = '' } = linearInfo || {} const { width, height } = imageSize || {} if (type !== 'linear') return // 角度计算 let angle = +(linearMap.get(direction) || direction.match(/(-?\d+(\.\d+)?)deg/)?.[1] || 180) % 360 // 对角线角度计算 if (layoutInfo && diagonalAngleMap[direction] && imageSize && linearInfo) { angle = radToAngle(diagonalAngleMap[direction](width, height)) || 180 } // 赋值 imageProps.colors = colors imageProps.locations = locations imageProps.angle = angle } const imageStyleToProps = (preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) => { // 初始化 const imageProps: ImageProps = { resizeMode: 'cover', style: { position: 'absolute' // ...StyleSheet.absoluteFillObject }, colors: [] } applyHandlers([backgroundSize, backgroundImage, backgroundPosition, linearGradient], [imageProps, preImageInfo, imageSize, layoutInfo]) return imageProps } function isHorizontal (val: PositionVal): val is 'left' | 'right' { return typeof val === 'string' && /^(left|right)$/.test(val) } function isVertical (val: PositionVal): val is 'top' | 'bottom' { return typeof val === 'string' && /^(top|bottom)$/.test(val) } function normalizeBackgroundPosition (parts: PositionVal[]): backgroundPositionList { if (parts.length === 0) return [] // 定义默认值 let hStart: 'left' | 'right' = 'left' let hOffset: PositionVal = 0 let vStart: 'top' | 'bottom' = 'top' let vOffset: PositionVal = 0 if (!Array.isArray(parts)) { // 模板 style 属性传入单个数值时不会和 class 一样转成数组,需要手动转换 parts = [parts] } // 模板 style 属性传入时, 需要额外转换处理单位 px/rpx/vh 以及 center 转化为 50% parts = (parts as (PositionVal | string)[]).map((part) => { if (typeof part !== 'string') return part if (part === 'center') return '50%' return global.__formatValue(part) as PositionVal }) if (parts.length === 4) return parts as backgroundPositionList // 归一化 if (parts.length === 1) { // 1. center // 2. 2px - hOffset, vOffset(center) - center为50% // 3. 10% - hOffset, vOffset(center) - center为50% // 4. left - hStart, vOffset(center) - center为50% // 5. top - hOffset(center), vStart - center为50% if (isHorizontal(parts[0])) { hStart = parts[0] vOffset = '50%' } else if (isVertical(parts[0])) { vStart = parts[0] hOffset = '50%' } else { hOffset = parts[0] vOffset = '50%' } } else if (parts.length === 2) { // 1. center center - hOffset, vOffset // 2. 10px center - hOffset, vStart // 3. left center - hStart, vOffset // 4. right center - hStart, vOffset // 5. 第一位是 left right 覆盖的是 hStart // center, 100% 正常的px 覆盖的是 hOffset // 第二位是 top bottom 覆盖的是 vStart // center, 100% 覆盖的是 vOffset // // 水平方向 if (isHorizontal(parts[0])) { hStart = parts[0] } else { // center, 100% 正常的px 覆盖的是 hOffset hOffset = parts[0] } // 垂直方向 if (isVertical(parts[1])) { vStart = parts[1] } else { // center, 100% 正常的px 覆盖的是 hOffset vOffset = parts[1] } } else if (parts.length === 3) { // 1. center top 10px / top 10px center 等价 - center为50% // 2. right 10px center / center right 10px 等价 - center为50% // 2. bottom 50px right if (typeof parts[0] === 'string' && typeof parts[1] === 'string' && /^left|bottom|right|top$/.test(parts[0]) && /^left|bottom|right|top$/.test(parts[1])) { [hStart, vStart, vOffset] = parts as ['left' | 'right', 'top' | 'bottom', number] } else { [hStart, hOffset, vStart] = parts as ['left' | 'right', number, 'top' | 'bottom'] } } return [hStart, hOffset, vStart, vOffset] as backgroundPositionList } /** * * calcSteps - 计算起始位置和终点位置之间的差值 * startVal - 起始位置距离 * endVal - 终点位置距离 * len - 数量 * **/ function calcSteps (startVal: number, endVal: number, len: number) { const diffVal = endVal - startVal const step = diffVal / len const newArr: Array = [] for (let i = 1; i < len; i++) { const val = startVal + step * i newArr.push(+val.toFixed(2)) } return newArr } function parseLinearGradient (text: string): LinearInfo | undefined { let linearText = text.trim().match(/linear-gradient\((.*)\)/)?.[1] if (!linearText) return // 添加默认的角度 if (!/^to|^-?\d+deg/.test(linearText)) { linearText = '180deg ,' + linearText } else { linearText = linearText.replace('to', '') } // 把 0deg, red 10%, blue 20% 解析为 ['0deg', 'red, 10%', 'blue, 20%'] const [direction, ...colorList] = linearText.split(/,(?![^(#]*\))/) // 记录需要填充起点的起始位置 let startIdx = 0; let startVal = 0 // 把 ['red, 10%', 'blue, 20%']解析为 [[red, 10%], [blue, 20%]] const linearInfo = colorList.map(item => item.trim().split(/(?((prev, cur, idx, self) => { const { colors, locations } = prev const [color, val] = cur let numberVal: number = parseFloat(val) / 100 // 处理渐变默认值 if (idx === 0) { numberVal = numberVal || 0 } else if (self.length - 1 === idx) { numberVal = numberVal || 1 } // 出现缺省值时进行填充 if (idx - startIdx > 1 && !isNaN(numberVal)) { locations.push(...calcSteps(startVal, numberVal, idx - startIdx)) } if (!isNaN(numberVal)) { startIdx = idx startVal = numberVal } // 添加color的数组 colors.push(color.trim()) !isNaN(numberVal) && locations.push(numberVal) return prev }, { colors: [], locations: [] }) return extendObject({}, linearInfo, { direction: direction.trim() }) } function parseBgImage (text: string): { linearInfo?: LinearInfo direction?: string type?: 'image' | 'linear' src?: string } { if (!text || text === 'none') return {} const src = parseUrl(text) if (src) return { src, type: 'image' } const linearInfo = parseLinearGradient(text) if (!linearInfo) return {} return { linearInfo, type: 'linear' } } function normalizeBackgroundSize ( backgroundSize: NonNullable, type: 'image' | 'linear' | undefined ): DimensionValue[] { const sizeList = backgroundSize.slice() if (sizeList.length === 1) sizeList.push('auto') return sizeList.map((val) => { if (typeof val !== 'string') return val // 处理当使用渐变的时候,background-size出现cover, contain, auto,当作100%处理 if (type === 'linear' && /^cover|contain|auto$/.test(val)) { val = '100%' } // 模板 style 属性传入时, 需要额外转换处理单位 px/rpx/vh return global.__formatValue(val) as DimensionValue }) } function preParseImage (imageStyle?: ExtendedViewStyle) { const { backgroundImage = '', backgroundSize = ['auto'], backgroundPosition = [0, 0] } = normalizeStyle(imageStyle) || {} const { type, src, linearInfo } = parseBgImage(backgroundImage) return { src, linearInfo, type, sizeList: normalizeBackgroundSize(backgroundSize, type), backgroundPosition: normalizeBackgroundPosition(backgroundPosition) } } function isDiagonalAngle (linearInfo?: LinearInfo): boolean { return !!(linearInfo?.direction && diagonalAngleMap[linearInfo.direction]) } function inheritStyle (innerStyle: ExtendedViewStyle = {}) { const { borderWidth, borderRadius } = innerStyle const borderStyles = ['borderRadius', 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'] return pickStyle(innerStyle, borderStyles, borderWidth && borderRadius ? (key, val) => { // 盒子内圆角borderWith与borderRadius的关系 // 当borderRadius 小于 当borderWith 内边框为直角 // 当borderRadius 大于等于 当borderWith 内边框为圆角 if (borderStyles.includes(key)) { const borderVal = +val - borderWidth return borderVal > 0 ? borderVal : 0 } return val } : undefined) } function useWrapImage (imageStyle?: ExtendedViewStyle, innerStyle?: Record, enableFastImage?: boolean) { // 预处理数据 const preImageInfo: PreImageInfo = preParseImage(imageStyle) // 预解析 const { src, sizeList, type } = preImageInfo // 判断是否可挂载onLayout const { needLayout, needImageSize } = checkNeedLayout(preImageInfo) const [show, setShow] = useState(((type === 'image' && !!src) || type === 'linear') && !needLayout && !needImageSize) const [, setImageSizeWidth] = useState(null) const [, setImageSizeHeight] = useState(null) const [, setLayoutInfoWidth] = useState(null) const [, setLayoutInfoHeight] = useState(null) const sizeInfo = useRef(null) const layoutInfo = useRef(null) useEffect(() => { sizeInfo.current = null if (type === 'linear') { if (!needLayout) setShow(true) return } if (!src) { setShow(false) return // 一开始未出现,数据改变时出现 } else if (!(needLayout || needImageSize)) { setShow(true) return } if (needImageSize) { Image.getSize(src, (width, height) => { sizeInfo.current = { width, height } // 1. 当需要绑定onLayout 2. 获取到布局信息 if (!needLayout || layoutInfo.current) { setImageSizeWidth(width) setImageSizeHeight(height) if (layoutInfo.current) { setLayoutInfoWidth(layoutInfo.current.width) setLayoutInfoHeight(layoutInfo.current.height) } setShow(true) } }) } // type 添加type 处理无渐变 有渐变的场景 }, [src, type]) if (!type) return null const onLayout = (res: LayoutChangeEvent) => { const { width, height } = res?.nativeEvent?.layout || {} layoutInfo.current = { width, height } if (!needImageSize) { setLayoutInfoWidth(width) setLayoutInfoHeight(height) // 有渐变角度的时候,才触发渲染组件 if (type === 'linear') { sizeInfo.current = { width: calcPercent(sizeList[0] as NumberVal, width), height: calcPercent(sizeList[1] as NumberVal, height) } setImageSizeWidth(sizeInfo.current.width) setImageSizeHeight(sizeInfo.current.height) } setShow(true) } else if (sizeInfo.current) { setLayoutInfoWidth(width) setLayoutInfoHeight(height) setImageSizeWidth(sizeInfo.current.width) setImageSizeHeight(sizeInfo.current.height) setShow(true) } } const backgroundProps: ViewProps = extendObject({ key: 'backgroundImage' }, needLayout ? { onLayout } : {}, { style: extendObject({}, inheritStyle(innerStyle), StyleSheet.absoluteFillObject, { overflow: 'hidden' as const }) } ) return createElement(View, backgroundProps, show && type === 'linear' && createElement(LinearGradient, extendObject({ useAngle: true }, imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size))), show && type === 'image' && renderImage(imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size), enableFastImage) ) } interface WrapChildrenConfig { hasVarDec: boolean enableBackground?: boolean textStyle?: TextStyle backgroundStyle?: ExtendedViewStyle varContext?: Record textProps?: Record innerStyle?: Record enableFastImage?: boolean } function wrapWithChildren (props: _ViewProps, { hasVarDec, enableBackground, textStyle, backgroundStyle, varContext, textProps, innerStyle, enableFastImage }: WrapChildrenConfig) { const children = wrapChildren(props, { hasVarDec, varContext, textStyle, textProps }) return [ // eslint-disable-next-line react-hooks/rules-of-hooks enableBackground ? useWrapImage(backgroundStyle, innerStyle, enableFastImage) : null, children ] } const _View = forwardRef, _ViewProps>((viewProps, ref): JSX.Element => { const { textProps, innerProps: props = {} } = splitProps(viewProps) let { style = {}, 'hover-style': hoverStyle, 'hover-start-time': hoverStartTime = 50, 'hover-stay-time': hoverStayTime = 400, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'enable-background': enableBackground, 'enable-fast-image': enableFastImage, 'enable-animation': enableAnimation, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, animation, catchtransitionend, bindtransitionend } = props // 默认样式 const defaultStyle: ExtendedViewStyle = style.display === 'flex' ? { flexDirection: 'row', flexBasis: 'auto', flexShrink: 1, flexWrap: 'nowrap' } : {} const enableHover = !!hoverStyle const { isHover, gesture } = useHover({ enableHover, hoverStartTime, hoverStayTime }) const styleObj: ExtendedViewStyle = extendObject({}, defaultStyle, style, isHover ? hoverStyle as ExtendedViewStyle : {}) const { normalStyle, hasSelfPercent, hasPositionFixed, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight }) const { textStyle, backgroundStyle, innerStyle = {} } = splitStyle(normalStyle) enableBackground = enableBackground || !!backgroundStyle const enableBackgroundRef = useRef(enableBackground) if (enableBackgroundRef.current !== enableBackground) { error('[Mpx runtime error]: background use should be stable in the component lifecycle, or you can set [enable-background] with true.') } const nodeRef = useRef(null) useNodesRef(props, ref, nodeRef, { style: normalStyle }) const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef }) const viewStyle = extendObject({}, innerStyle, layoutStyle) const transitionend = isFunction(catchtransitionend) ? catchtransitionend : isFunction(bindtransitionend) ? bindtransitionend : undefined const { enableStyleAnimation, animationStyle } = useAnimationHooks({ layoutRef, animation, enableAnimation, style: viewStyle, transitionend }) const innerProps = useInnerProps( extendObject( {}, props, layoutProps, { ref: nodeRef, style: enableStyleAnimation ? [viewStyle, animationStyle] : viewStyle } ), [ 'hover-start-time', 'hover-stay-time', 'hover-style', 'hover-class' ], { layoutRef } ) const childNode = wrapWithChildren(props, { hasVarDec, enableBackground: enableBackgroundRef.current, textStyle, backgroundStyle, varContext: varContextRef.current, textProps, innerStyle, enableFastImage }) let finalComponent: JSX.Element = enableStyleAnimation ? createElement(Animated.View, innerProps, childNode) : createElement(View, innerProps, childNode) if (enableHover) { finalComponent = createElement(GestureDetector, { gesture: gesture as PanGesture }, finalComponent) } if (hasPositionFixed) { finalComponent = createElement(Portal, null, finalComponent) } return finalComponent }) _View.displayName = 'MpxView' export default _View