diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 8afb4e3da..d5a1f711e 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -143,7 +143,11 @@ Mpx.config = { hostWhitelists Array 类型 支持h5域名白名单安全校验 apiImplementations webview JSSDK接口 例如getlocation */ - webviewConfig: {} + webviewConfig: {}, + /** + * react-native 相关配置,用于挂载事件等,如 onShareAppMessage + */ + rnConfig: {} } global.__mpx = Mpx diff --git a/packages/webpack-plugin/lib/platform/template/wx/component-config/button.js b/packages/webpack-plugin/lib/platform/template/wx/component-config/button.js index f58bbbd52..1757a228a 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/component-config/button.js +++ b/packages/webpack-plugin/lib/platform/template/wx/component-config/button.js @@ -30,8 +30,10 @@ module.exports = function ({ print }) { const webEventLog = print({ platform: 'web', tag: TAG_NAME, isError: false, type: 'event' }) const qaPropLog = print({ platform: 'qa', tag: TAG_NAME, isError: false }) const wxPropValueLog = print({ platform: 'wx', tag: TAG_NAME, isError: false, type: 'value' }) + const iosValueLogError = print({ platform: 'ios', tag: TAG_NAME, isError: true, type: 'value' }) const iosPropLog = print({ platform: 'ios', tag: TAG_NAME, isError: false }) const iosEventLog = print({ platform: 'ios', tag: TAG_NAME, isError: false, type: 'event' }) + const androidValueLogError = print({ platform: 'android', tag: TAG_NAME, isError: true, type: 'value' }) const androidPropLog = print({ platform: 'android', tag: TAG_NAME, isError: false }) const androidEventLog = print({ platform: 'android', tag: TAG_NAME, isError: false, type: 'event' }) @@ -119,6 +121,18 @@ module.exports = function ({ print }) { ttValueLogError({ name, value }) } } + }, + ios ({ name, value }) { + const supported = ['share'] + if (!supported.includes(value)) { + iosValueLogError({ name, value }) + } + }, + android ({ name, value }) { + const supported = ['share'] + if (!supported.includes(value)) { + androidValueLogError({ name, value }) + } } }, { @@ -157,7 +171,7 @@ module.exports = function ({ print }) { qa: qaPropLog }, { - test: /^(open-type|lang|from-type|hover-class|send-message-title|send-message-path|send-message-img|app-parameter|show-message-card|phone-number-no-quota-toast|bindgetuserinfo|bindcontact|createliveactivity|bindgetphonenumber|bindgetrealtimephonenumber|binderror|bindopensetting|bindlaunchapp|bindchooseavatar|bindagreeprivacyauthorization)$/, + test: /^(lang|from-type|hover-class|send-message-title|send-message-path|send-message-img|app-parameter|show-message-card|phone-number-no-quota-toast|bindgetuserinfo|bindcontact|createliveactivity|bindgetphonenumber|bindgetrealtimephonenumber|binderror|bindopensetting|bindlaunchapp|bindchooseavatar|bindagreeprivacyauthorization)$/, ios: iosPropLog, android: androidPropLog } diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-button.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-button.tsx index 22902d599..7aa5db3a4 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-button.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-button.tsx @@ -5,9 +5,9 @@ * ✔ disabled * ✔ loading * ✘ form-type - * ✘ open-type - * - hover-class Only support 'none' - * ✔ hover-style: Convert hoverClass to hoverStyle. + * - open-type: Partially. Only support `share`、`getUserInfo` + * ✔ hover-class: Convert hoverClass to hoverStyle. + * ✔ hover-style * ✘ hover-stop-propagation * ✔ hover-start-time * ✔ hover-stay-time @@ -34,10 +34,15 @@ * ✘ bindagreeprivacyauthorization * ✔ bindtap */ -import React, { useEffect, useMemo, useRef, useState, ReactNode, useCallback, forwardRef } from 'react' +import React, { + useEffect, + useMemo, + useRef, + useState, + ReactNode, + forwardRef, +} from 'react' import { - TouchableWithoutFeedback, - GestureResponderEvent, View, Text, StyleSheet, @@ -46,9 +51,23 @@ import { TextStyle, Animated, Easing, + LayoutChangeEvent, + NativeSyntheticEvent, } from 'react-native' import { extractTextStyle } from './utils' -import useInnerTouchable, { getCustomEvent } from './getInnerListeners' +import useInnerProps, { getCustomEvent } from './getInnerListeners' +import useNodesRef from '../../useNodesRef' + +export type Type = 'default' | 'primary' | 'warn' + +/** + * normal、hover、plain、disabled + */ +type TypeColor = [string, string, string, string] + +export type OpenType = 'share' | 'getUserInfo' + +export type OpenTypeEvent = 'onShareAppMessage' | 'onUserInfo' export interface ButtonProps { size?: string @@ -56,24 +75,22 @@ export interface ButtonProps { plain?: boolean disabled?: boolean loading?: boolean - 'hover-class'?: 'none' + 'hover-class'?: string 'hover-style'?: StyleProp 'hover-start-time'?: number 'hover-stay-time'?: number + 'open-type'?: OpenType + 'data-shareInfo'?: unknown style?: StyleProp children: ReactNode - bindtap?: (evt: GestureResponderEvent | unknown) => void - catchtap?: (evt: GestureResponderEvent | unknown) => void + bindgetuserinfo?: (userInfo: any) => void + bindtap?: (evt: NativeSyntheticEvent | unknown) => void + catchtap?: (evt: NativeSyntheticEvent | unknown) => void + bindtouchstart?: (evt: NativeSyntheticEvent | unknown) => void + bindtouchend?: (evt: NativeSyntheticEvent | unknown) => void } -export type Type = 'default' | 'primary' | 'warn' - -/** - * normal、hover、plain、disabled - */ -type TypeColor = [string, string, string, string] - -const LoadingImageUri = +const LOADING_IMAGE_URI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAB8hJREFUeJztnVtsFFUch6ltUYrEAi0Qo40xChGM+oAGI0EEKl4QfDVI9AkqqQZ4IVA1RSIvJlwUWwqJUokGKMVYwHJTq4mGuA+SxpJYggJSSgMpVFOtvbh+J84mk+3smXN2znZm2fNLvoQH5uQ/v4+Z2Z3dHUaNsrGxsbGxsbGxsbGxsbGxsTGSrq6uUqiHqw7iz6Vhz5WzofwYxJP4Mey5cjIUX+4hI0F52PPlXCi9WiKkOuz5ci5WiMFcvHhxOXRCHPpgLdyis4ZJITtqagtgPfRBHH6HV3XWyNpQ/DxHRDJbddYxLKTGEZHMLK2dy8ZQ/O4UQgQzVdcxJYTSZ6aQIfggrZ3MplD6CYmQmOo6BoXEJEK+TGsnsymUXicRIlimso4JIRS+TCJDsD3QzmZDKHwqDEmEdECR3zpBhVB2EVyWyBiC+4zsdNRD4Vt8jpJ3/dYwIGSTz9Gx2cjOZkMofBx0S4SIl8JlsjWCCKHsMuiXyOiGcUZ3Ouqh8BU+R0mjbPuAQg76HB3Lje5sNoTC86DNR8qcVNunK4Sy5/jIaIO8jOx01CMK9xEihHmWk44Qis53CpcJSfmPICdC4Q0+Ul7z2i5NISt9ZOzP6M5mQ8TF27mIpxIiLv7DLrC6t9/FRdq5WKeSIe5jSV9IZEXa29sfgC+gBXbBJN01KPwdn6PkLa/tKP6Uh4xvvP4uZW/wOTo26M69q27nZPgIWqARpumuYTSU/zT0Q9xFL6yFQtV1KHyM6+6vF4e9tuvS+AiXwo9JZIg3iGNU56X4QlgPvRB30QdPqa5jNBSeBxeSZLg5B0tU16P0pRIhnwadl8L3SoS8pLoOhS+Bc0ki3JwNOmtaoeyJEhluTojTmsqaFP99CiGzg85L6QtTyGhR2Z6ip8PXEhFuioPOqx1Kvg3+VZQyBLUwXrYmxU+Bky4Rl+BlUzNTfgV0umSI01iJbBvKnQC1MKQoY0Cc0kzNrBUK3qMoJEE3VEK+bF0kPA4PZmpuJDwCj8n+DqXmQyX0KIpIUJepuX1DsXfAPk0pgp8hnIufQih1AZzRFCH4DHzvVGc8lDsbWtMQ0yikhj1/IuLc77x81RXRCoGvc0ZDsbdAhXNa0pGyO+zZE6HUfZoirkEFaH1BY0TjnMa2wKCikL9hdNhzU+pYjQv3ILwH2XOLnpKnQrOilDvDnpdy71KU0QT3hz1v2qHsRXBWIuOSON2FPafzqqpD9oYPFoY9p5FQeAGsgRtJMgbgubDnS4TCFzmnI7eI6/AGFIQ9n/FQfimsgsNwEGaEPVNyKP5h57R0GF6HiWHPZGNjY2NjYzytra2FsBiqoFqTKmfbcO6EppE99Z8UwmKogmpNqpxtM7O/FFkMpyEeELHGyH9eoBmKLIbTEA+IWMP8/lLiNgMyEmwxPqDhUOI2AzISmN9fSrxiUMh54wMaDiVeMSjkvPEBrZDoCanNsVNWbdRPWSUGL+q3Gx/QcCixxOBFPTP722pf9kbnZa+NjY2NjU2YicViJbADWqAJpoc9U3Ia9u1/CA5BC+wA6TcbszIUXwCr4QbEXQzAM2HPlwjlvwCDEHdxHVbDzfERLoU/D+1JItxchtC/5EDh+XA5SYabXyB7n8NFyVOhWSLCTehfA6LsuyUy3ByB7PkaEOUWw/swqChDEPoXzii5WFFI3DmtbYbIfA12WMRpByrgmoYIwZ6wZ0+Eghs1pAiuQQVE62fUlPoktGqKEDRE4ehIhGLHw0FNKYKf4Imw5xcixsHeNES0wfyw508Vyl0AZ9IQsxfGhjY4pX6sKaIbKkH6g53vWr6dBXNB+xe9fmlqapoEc0H6tDjnVVcl9GhKqTE9s1IodbTzPkJFxBBsB+lFEAFT4CTEHXrgFVMzI2E59ELc4ShI3/hR8ATYDkOKQnpMzasVyp2oKONETPEdOeX/4JLhJvCzDyl+vkuEmxaV7Sl6BnylKEX6W8qMhJLz4DeJiF9B+WfRlL40hQzBh0Hnpfj6FEIES1XXoewX4YJERjg/ixah8HKP09YfsAaUP5ih8CLokAg55LXd8aPHSqEerjqIP3s+OIDSmyVCOkD5t4GUfiusg94kGf0wT3WdjEScjuBzOAKrQPtCTOEbJTIEb3ttR/kxiCfh+ex3Ct8gESLYqDs35U9u+P8+l3j3fgDCfbSGiVB2GfRJZHTDsPcqFF/uISPBsHtOFD4euiVC+iD7Hz4TNJR9wOfo8Hw8E6VXS4RUe21D4St9jpKGjO5s1EPZc3xktIHnbYk0heRDm4+U3HyAmSjaKVwmJGU56QgREYX7CBHConVvaiRC2RU+MqQPwUxXiAiFH/SRssLozkY94iLtXKxTyRAXeekFNqCQMuiXCBEX/8jc9Mx4KHurz9Hh+yDlIEJEKHyTz1GSGw9SpuxpMCCR0SneKPqtY0BIEXRKhIgj6F4jOx3lUHadz9Gh9DD+oEJEKHyZz1Fy8z+Mn8KPS2Qo/3cVJoSIUHpMIqQ5rZ3MplD6TokQ5f/QxaCQRyVCAt/UjHyca4jXrRKt/83GlBARiq/xkPEn3KOzTtaG8p+FLkfEX7AOtL6bZVhIAbwJ/zgyLkFkP2KOZEwKsTEQKyRi0b39bjMCofhTHjI8n/1uMwI5rvERro2NjY2NjY2NjY2NjY2NjY1+/gNWA2LIOT/TRAAAAABJRU5ErkJggg==' const TypeColorMap: Record = { @@ -82,14 +99,17 @@ const TypeColorMap: Record = { warn: ['#E64340', '#CE3C39', '230,67,64', '#EC8B89'], } +const OpenTypeEventsMap = new Map([ + ['share', 'onShareAppMessage'], + ['getUserInfo', 'onUserInfo'], +]) + const styles = StyleSheet.create({ button: { width: '100%', // flexDirection: 'row', css 默认 block justifyContent: 'center', alignItems: 'center', - // paddingHorizontal: 14, - // marginVertical: 14, height: 46, borderRadius: 5, backgroundColor: '#F8F8F8', @@ -144,7 +164,7 @@ const Loading = ({ alone = false }: { alone: boolean }): React.JSX.Element => { marginRight: alone ? 0 : 5, } - return + return } const Button = forwardRef((props, ref): React.JSX.Element => { @@ -155,35 +175,39 @@ const Button = forwardRef((props, ref): React.JSX.Element => disabled = false, loading = false, 'hover-class': hoverClass, - 'hover-style': hoverStyle = {}, + 'hover-style': hoverStyle = [], 'hover-start-time': hoverStartTime = 20, 'hover-stay-time': hoverStayTime = 70, + 'open-type': openType, + 'data-shareInfo': shareinfo, style = [], children, - bindtap = () => {}, - catchtap = () => {}, + bindgetuserinfo, + bindtap, + catchtap, + bindtouchstart, + bindtouchend, } = props + const { nodeRef } = useNodesRef(props, ref) + const refs = useRef<{ - preseeInTimer: ReturnType | undefined - preseeOutTimer: ReturnType | undefined - isPressEnd: boolean + hoverStartTimer: ReturnType | undefined + hoverStayTimer: ReturnType | undefined }>({ - preseeInTimer: undefined, - preseeOutTimer: undefined, - isPressEnd: false, + hoverStartTimer: undefined, + hoverStayTimer: undefined, }) + const layoutRef = useRef({}) + const [isHover, setIsHover] = useState(false) const isMiniSize = size === 'mini' const applyHoverEffect = isHover && hoverClass !== 'none' - // mpx 处理后 style 是数组,这里先打平一下 - const styleObj = Object.assign({}, ...style) - - const { viewStyle: presetViewStyle, textStyle: presetTextStyle } = useMemo<{ + const { viewStyle, textStyle } = useMemo<{ viewStyle: ViewStyle textStyle: TextStyle }>(() => { @@ -204,8 +228,8 @@ const Button = forwardRef((props, ref): React.JSX.Element => type === 'default' ? `rgba(0, 0, 0, ${disabled ? 0.3 : applyHoverEffect || loading ? 0.6 : 1})` : `rgba(255 ,255 ,255 , ${disabled || applyHoverEffect || loading ? 0.6 : 1})` - // 从 view 中取 text 可继承的样式属性 - const inheritTextStyle = extractTextStyle(applyHoverEffect ? [styleObj, hoverStyle] : styleObj) + const inheritTextStyle = extractTextStyle(style) + const inheritTextHoverStyle = extractTextStyle(applyHoverEffect ? hoverStyle : []) return { viewStyle: { borderWidth: 1, @@ -215,80 +239,136 @@ const Button = forwardRef((props, ref): React.JSX.Element => }, textStyle: { color: plain ? plainTextColor : normalTextColor, - ...inheritTextStyle + ...inheritTextStyle, + ...inheritTextHoverStyle + } + } + }, [type, plain, applyHoverEffect, loading, disabled, style, hoverStyle]) + + const getOpenTypeEvent = () => { + if (!openType) return + if (!global?.__mpx?.config?.rnConfig) { + console.warn('Environment not supported') + return + } + + const eventName = OpenTypeEventsMap.get(openType) + if (!eventName) { + console.warn(`open-type not support ${openType}`) + return + } + + const event = global?.__mpx?.config?.rnConfig?.[eventName] + if (!event) { + console.warn(`Unregistered ${eventName} event`) + return + } + + return event + } + + const handleOpenTypeEvent = () => { + if (!openType) return + if (openType === 'share') { + const onShareAppMessage = getOpenTypeEvent() + onShareAppMessage && onShareAppMessage({ + from: 'button', + target: { + dataset: { shareinfo } + } + }) + } + + if (openType === 'getUserInfo') { + const onUserInfo = getOpenTypeEvent() + const userInfo = onUserInfo && onUserInfo() + if (typeof userInfo === 'object') { + bindgetuserinfo && bindgetuserinfo(userInfo) } } - }, [type, plain, applyHoverEffect, loading, disabled, styleObj]) + } - const stopHover = useCallback(() => { - refs.current.preseeOutTimer = setTimeout(() => { + const setStayTimer = () => { + clearTimeout(refs.current.hoverStayTimer) + refs.current.hoverStayTimer = setTimeout(() => { setIsHover(false) - clearTimeout(refs.current.preseeOutTimer) + clearTimeout(refs.current.hoverStayTimer) }, hoverStayTime) - }, [hoverStayTime]) + } - const onPressIn = () => { - refs.current.isPressEnd = false - refs.current.preseeInTimer = setTimeout(() => { + const setStartTimer = () => { + clearTimeout(refs.current.hoverStartTimer) + refs.current.hoverStartTimer = setTimeout(() => { setIsHover(true) - clearTimeout(refs.current.preseeInTimer) + clearTimeout(refs.current.hoverStartTimer) }, hoverStartTime) } - const onPressOut = () => { - refs.current.isPressEnd = true - stopHover() + const onTouchStart = (evt: NativeSyntheticEvent) => { + bindtouchstart && bindtouchstart(evt) + if (disabled) return + setStartTimer() } - const onPress = (evt: GestureResponderEvent) => { - !disabled && bindtap(getCustomEvent('tap', evt, {}, props)) + const onTouchEnd = (evt: NativeSyntheticEvent) => { + bindtouchend && bindtouchend(evt) + if (disabled) return + setStayTimer() } - const catchPress = (evt: GestureResponderEvent) => { - !disabled && catchtap(getCustomEvent('tap', evt, {}, props)) + const onTap = (evt: NativeSyntheticEvent) => { + if (disabled) return + bindtap && bindtap(getCustomEvent('tap', evt, { layoutRef }, props)) + handleOpenTypeEvent() } - useEffect(() => { - isHover && refs.current.isPressEnd && stopHover() - }, [isHover, stopHover]) + const catchTap = (evt: NativeSyntheticEvent) => { + if (disabled) return + catchtap && catchtap(getCustomEvent('tap', evt, { layoutRef }, props)) + handleOpenTypeEvent() + } - const innerTouchable = useInnerTouchable({ - ...props, - bindtap: () => {}, - catchtap: catchPress, - }) + const onLayout = () => { + nodeRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => { + layoutRef.current = { x, y, width, height, offsetLeft, offsetTop } + }) + } + + const innerProps = useInnerProps( + props, + { + ref: nodeRef, + onLayout, + bindtouchstart: onTouchStart, + bindtouchend: onTouchEnd, + bindtap: onTap, + catchtap: catchTap, + }, + [], + { + layoutRef + } + ); return ( - - - {loading && } - {['string', 'number'].includes(typeof children) ? ( - - {children} - - ) : ( - children - )} - - + style={[ + styles.button, + isMiniSize && styles.buttonMini, + viewStyle, + style, + applyHoverEffect && hoverStyle, + ]}> + {loading && } + + {children} + + ) }) -Button.displayName = '_Button' +Button.displayName = 'mpx-button' export default Button diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-image/index.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-image/index.tsx index e2db29e95..46355378c 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-image/index.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-image/index.tsx @@ -10,11 +10,17 @@ * ✔ bindtap * ✔ DEFAULT_SIZE */ -import React, { useCallback, useEffect, useMemo, useState, forwardRef } from 'react' +import React, { + useCallback, + useEffect, + useMemo, + useState, + forwardRef, + useRef, +} from 'react' import { Image as RNImage, View, - Text, ImageStyle, StyleProp, ImageSourcePropType, @@ -26,8 +32,8 @@ import { DimensionValue, ImageLoadEventData, } from 'react-native' -import { omit } from '../utils' -import useInnerTouchable, { getCustomEvent } from '../getInnerListeners' +import useInnerProps, { getCustomEvent } from '../getInnerListeners' +import useNodesRef from '../../../useNodesRef' export type Mode = | 'scaleToFill' @@ -58,17 +64,17 @@ export interface ImageProps { const DEFAULT_IMAGE_WIDTH = 320 const DEFAULT_IMAGE_HEIGHT = 240 -const REMOTE_SVG_REGEXP = /https?:\/\/.*\.(?:svg)/i - -const styls = StyleSheet.create({ - suspense: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - width: '100%', - height: '100%', - }, -}) +// const REMOTE_SVG_REGEXP = /https?:\/\/.*\.(?:svg)/i + +// const styls = StyleSheet.create({ +// suspense: { +// display: 'flex', +// justifyContent: 'center', +// alignItems: 'center', +// width: '100%', +// height: '100%', +// }, +// }) const cropMode: Mode[] = [ 'top', @@ -97,11 +103,11 @@ const relativeCenteredSize = (viewSize: number, imageSize: number) => (viewSize // const Svg = lazy(() => import('./svg')) -const Fallback = ( - - loading ... - -) +// const Fallback = ( +// +// loading ... +// +// ) const Image = forwardRef((props, ref): React.JSX.Element => { const { @@ -110,10 +116,12 @@ const Image = forwardRef((props, ref): React.JSX.Element => svg = false, style = {}, bindload, - binderror, - ...restProps - } = omit(props, ['source', 'resizeeMode']) - const innerTouchable = useInnerTouchable(restProps) + binderror + } = props + + const { nodeRef } = useNodesRef(props, ref) + + const layoutRef = useRef({}) const { width = DEFAULT_IMAGE_WIDTH, height = DEFAULT_IMAGE_HEIGHT } = StyleSheet.flatten(style) @@ -166,15 +174,6 @@ const Image = forwardRef((props, ref): React.JSX.Element => } }, [mode, viewWidth, viewHeight, imageWidth, imageHeight]) - const onViewLayout = ({ - nativeEvent: { - layout: { width, height }, - }, - }: LayoutChangeEvent) => { - setViewWidth(width) - setViewHeight(height) - } - const onImageLoad = (evt: NativeSyntheticEvent) => { if (!bindload) return if (typeof src === 'string') { @@ -185,6 +184,7 @@ const Image = forwardRef((props, ref): React.JSX.Element => evt, { detail: { width, height }, + layoutRef }, props ) @@ -198,6 +198,7 @@ const Image = forwardRef((props, ref): React.JSX.Element => evt, { detail: { width, height }, + layoutRef }, props ) @@ -213,12 +214,28 @@ const Image = forwardRef((props, ref): React.JSX.Element => evt, { detail: { errMsg: evt.nativeEvent.error }, + layoutRef }, props ) ) } + const onViewLayout = ({ + nativeEvent: { + layout: { width, height }, + }, + }: LayoutChangeEvent) => { + setViewWidth(width) + setViewHeight(height) + } + + const onImageLayout = () => { + nodeRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => { + layoutRef.current = { x, y, width, height, offsetLeft, offsetTop } + }) + } + const loadImage = useCallback((): void => { if (!isWidthFixMode && !isHeightFixMode && !isCropMode) return if (typeof src === 'string') { @@ -245,10 +262,20 @@ const Image = forwardRef((props, ref): React.JSX.Element => useEffect(() => loadImage(), [loadImage]) + const innerProps = useInnerProps(props, { + ref: nodeRef, + onLayout: onImageLayout + }, + [], + { + layoutRef + } +) + // if (typeof src === 'string' && REMOTE_SVG_REGEXP.test(src)) { // return ( - // - // + // + // // // // @@ -258,7 +285,7 @@ const Image = forwardRef((props, ref): React.JSX.Element => // if (svg) { // return ( // - // + // // // // @@ -278,7 +305,7 @@ const Image = forwardRef((props, ref): React.JSX.Element => ]} onLayout={onViewLayout}> ((props, ref): React.JSX.Element => ...(isCropMode && cropModeStyle), }, ]} - {...innerTouchable} /> ) }) -Image.displayName = '_Image' +Image.displayName = 'mpx-image' export default Image diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-input.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-input.tsx index f9599c879..03c9ddda9 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-input.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-input.tsx @@ -1,7 +1,7 @@ /** * ✔ value * ✔ defaultValue - * - type: Partially. Not support safe-password、nickname + * - type: Partially. Not support `safe-password`、`nickname` * ✔ password * ✔ placeholder * - placeholder-style: Only placeholderTextColor(RN). @@ -37,7 +37,7 @@ * ✘ bind:keyboardcompositionend * ✘ bind:onkeyboardheightchange */ -import React, { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react' +import React, { forwardRef, useMemo, useRef, useState } from 'react' import { KeyboardTypeOptions, Platform, @@ -54,9 +54,11 @@ import { TextInputFocusEventData, TextInputChangeEventData, TextInputSubmitEditingEventData, + LayoutChangeEvent, } from 'react-native' import { parseInlineStyle, useUpdateEffect } from './utils' -import useInnerTouchable, { getCustomEvent } from './getInnerListeners' +import useInnerProps, { getCustomEvent } from './getInnerListeners' +import useNodesRef from '../../useNodesRef' type InputStyle = Omit< TextStyle & ViewStyle & Pick, @@ -141,15 +143,16 @@ const Input = forwardRef((props: InputProps & PrivateInputProps, ref): React.JSX multiline, 'auto-height': autoHeight, bindlinechange, - ...restProps } = props + const { nodeRef } = useNodesRef(props, ref) + const keyboardType = keyboardTypeMap[type] const defaultValue = props.defaultValue ?? (type === 'number' && value ? value + '' : value) const placeholderTextColor = props.placeholderTextColor || parseInlineStyle(placeholderStyle)?.color const textAlignVertical = multiline ? 'top' : 'auto' - const inputRef = useRef(null) + const layoutRef = useRef({}) const tmpValue = useRef() const cursorIndex = useRef(0) const lineCount = useRef(0) @@ -186,6 +189,7 @@ const Input = forwardRef((props: InputProps & PrivateInputProps, ref): React.JSX value: evt.nativeEvent.text, cursor: cursorIndex.current, }, + layoutRef }, props ) @@ -208,6 +212,7 @@ const Input = forwardRef((props: InputProps & PrivateInputProps, ref): React.JSX detail: { value: tmpValue.current || '', }, + layoutRef }, props ) @@ -225,6 +230,7 @@ const Input = forwardRef((props: InputProps & PrivateInputProps, ref): React.JSX value: tmpValue.current || '', cursor: cursorIndex.current, }, + layoutRef }, props ) @@ -242,6 +248,7 @@ const Input = forwardRef((props: InputProps & PrivateInputProps, ref): React.JSX detail: { value: tmpValue.current || '', }, + layoutRef }, props ) @@ -259,6 +266,7 @@ const Input = forwardRef((props: InputProps & PrivateInputProps, ref): React.JSX detail: { value: tmpValue.current || '', }, + layoutRef }, props ) @@ -282,6 +290,7 @@ const Input = forwardRef((props: InputProps & PrivateInputProps, ref): React.JSX lineHeight, lineCount: lineCount.current, }, + layoutRef }, props ) @@ -301,46 +310,41 @@ const Input = forwardRef((props: InputProps & PrivateInputProps, ref): React.JSX selectionStart: evt.nativeEvent.selection.start, selectionEnd: evt.nativeEvent.selection.end, }, + layoutRef }, props ) ) } + const onLayout = () => { + nodeRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => { + layoutRef.current = { x, y, width, height, offsetLeft, offsetTop } + }) + } + useUpdateEffect(() => { - if (!inputRef?.current) { + if (!nodeRef?.current) { return } - focus ? inputRef.current.focus() : inputRef.current.blur() + focus + ? (nodeRef.current as TextInput)?.focus() + : (nodeRef.current as TextInput)?.blur() }, [focus]) - const innerTouchable = useInnerTouchable({ - ...props, + const innerProps = useInnerProps(props, { + ref: nodeRef, + onLayout + }, + [], + { + layoutRef }) - useImperativeHandle(ref, () => { - return { - ...props, - focus() { - inputRef.current?.focus() - }, - blur() { - inputRef.current?.blur() - }, - clear() { - inputRef.current?.clear() - }, - isFocused() { - inputRef.current?.isFocused() - }, - } - }) return ( +export type TextareProps = Omit< + InputProps & PrivateInputProps, + 'type' | 'password' | 'pass' | 'confirm-hold' +> -const Textarea = forwardRef((props, ref): React.JSX.Element => { - const restProps = omit(props, ['type', 'password', 'multiline', 'confirm-hold']) - return Keyboard.dismiss()} {...restProps} ref={ref} /> -}) +const Textarea = forwardRef( + (props, ref): React.JSX.Element => { + const restProps = omit(props, [ + 'ref', + 'type', + 'password', + 'multiline', + 'confirm-hold', + ]) + return ( + Keyboard.dismiss()} + {...restProps} + /> + ) + } +) -Textarea.displayName = '_Textarea' +Textarea.displayName = 'mpx-textarea' export default Textarea