UNPKG

@mpxjs/webpack-plugin

Version:

mpx compile core

276 lines (275 loc) 13 kB
/** * ✔ size * ✔ type * ✔ plain * ✔ disabled * ✔ loading * ✔ form-type * - open-type: Partially. Only support `share`、`getUserInfo` * ✔ hover-class: Convert hoverClass to hoverStyle. * ✔ hover-style * ✘ hover-stop-propagation * ✔ hover-start-time * ✔ hover-stay-time * ✘ lang * ✘ session-from * ✘ send-message-title * ✘ send-message-path * ✘ send-message-img * ✘ app-parameter * ✘ show-message-card * ✘ phone-number-no-quota-toast * ✘ bindgetuserinfo * ✘ bindcontact * ✘ createliveactivity * ✘ bindgetphonenumber * ✘ bindgetphonenumber * ✘ bindgetrealtimephonenumber * ✘ binderror * ✘ bindopensetting * ✘ bindlaunchapp * ✘ bindlaunchapp * ✘ bindchooseavatar * ✘ bindchooseavatar * ✘ bindagreeprivacyauthorization * ✔ bindtap */ import { createElement, useEffect, useRef, forwardRef, useContext } from 'react'; import { View, StyleSheet, Animated, Easing, useAnimatedValue } from 'react-native'; import { warn } from '@mpxjs/utils'; import { GestureDetector } from 'react-native-gesture-handler'; import { getCurrentPage, splitProps, splitStyle, useLayout, useTransformStyle, wrapChildren, extendObject, useHover } from './utils'; import useInnerProps, { getCustomEvent } from './getInnerListeners'; import useNodesRef from './useNodesRef'; import { RouteContext, FormContext } from './context'; import Portal from './mpx-portal'; const LOADING_IMAGE_URI = 'data:image/png;base64,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'; const TypeColorMap = { default: ['#F8F8F8', '#DEDEDE', '35,35,35', '#F7F7F7'], primary: ['#1AAD19', '#179B16', '26,173,25', '#9ED99D'], warn: ['#E64340', '#CE3C39', '230,67,64', '#EC8B89'] }; const OpenTypeEventsMap = new Map([ ['share', 'onShareAppMessage'], ['getUserInfo', 'onUserInfo'] ]); const styles = StyleSheet.create({ button: { width: '100%', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', height: 46, borderRadius: 5, backgroundColor: '#F8F8F8', marginHorizontal: 'auto' // 按钮默认居中 }, buttonMini: { height: 30 }, text: { fontSize: 18, color: '#000000' }, textMini: { fontSize: 13 }, loading: { width: 20, height: 20 } }); const getOpenTypeEvent = (openType) => { if (!openType) return; if (!global.__mpx?.config?.rnConfig) { warn('Environment not supported'); return; } const eventName = OpenTypeEventsMap.get(openType); if (!eventName) { warn(`open-type not support ${openType}`); return; } const event = global.__mpx.config.rnConfig.openTypeHandler?.[eventName]; if (!event) { warn(`Unregistered ${eventName} event`); return; } return event; }; const timer = (data, time = 3000) => new Promise((resolve) => { setTimeout(() => { resolve(data); }, time); }); const Loading = ({ alone = false }) => { const image = useAnimatedValue(0); const rotate = image.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] }); useEffect(() => { const animation = Animated.loop(Animated.timing(image, { toValue: 1, duration: 1000, easing: Easing.linear, useNativeDriver: true, isInteraction: false })); animation.start(); return () => { animation.stop(); }; }, []); const loadingStyle = extendObject({}, styles.loading, { transform: [{ rotate }], marginRight: alone ? 0 : 5 }); return <Animated.Image testID="loading" style={loadingStyle} source={{ uri: LOADING_IMAGE_URI }}/>; }; const Button = forwardRef((buttonProps, ref) => { const { textProps, innerProps: props = {} } = splitProps(buttonProps); const { size = 'default', type = 'default', plain = false, disabled = false, loading = false, 'hover-class': hoverClass, 'hover-style': hoverStyle = {}, 'hover-start-time': hoverStartTime = 20, 'hover-stay-time': hoverStayTime = 70, 'open-type': openType, 'form-type': formType, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, style = {}, children, bindgetuserinfo, bindtap } = props; const { pageId } = useContext(RouteContext) || {}; const formContext = useContext(FormContext); const enableHover = hoverClass !== 'none'; const { isHover, gesture } = useHover({ enableHover, hoverStartTime, hoverStayTime, disabled }); let submitFn; let resetFn; if (formContext) { submitFn = formContext.submit; resetFn = formContext.reset; } const isMiniSize = size === 'mini'; const [color, hoverColor, plainColor, disabledColor] = TypeColorMap[type]; const normalBackgroundColor = disabled ? disabledColor : isHover || loading ? hoverColor : color; const plainBorderColor = disabled ? 'rgba(0, 0, 0, .2)' : isHover ? `rgba(${plainColor},.6)` : `rgb(${plainColor})`; const normalBorderColor = type === 'default' ? 'rgba(0, 0, 0, .2)' : normalBackgroundColor; const plainTextColor = disabled ? 'rgba(0, 0, 0, .2)' : isHover ? `rgba(${plainColor}, .6)` : `rgb(${plainColor})`; const normalTextColor = type === 'default' ? `rgba(0, 0, 0, ${disabled ? 0.3 : isHover || loading ? 0.6 : 1})` : `rgba(255 ,255 ,255 , ${disabled || isHover || loading ? 0.6 : 1})`; const viewStyle = { borderWidth: 1, borderStyle: 'solid', borderColor: plain ? plainBorderColor : normalBorderColor, backgroundColor: plain ? 'transparent' : normalBackgroundColor }; const defaultViewStyle = extendObject({}, styles.button, isMiniSize ? styles.buttonMini : null, viewStyle); const defaultTextStyle = extendObject({}, styles.text, isMiniSize ? styles.textMini : {}, { color: plain ? plainTextColor : normalTextColor }); const defaultStyle = extendObject({}, defaultViewStyle, defaultTextStyle); const styleObj = extendObject({}, defaultStyle, style, isHover ? hoverStyle : {}); const { hasPositionFixed, hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight }); const nodeRef = useRef(null); useNodesRef(props, ref, nodeRef, { style: normalStyle }); const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef }); const { textStyle, backgroundStyle, innerStyle = {} } = splitStyle(normalStyle); if (backgroundStyle) { warn('Button does not support background image-related styles!'); } const handleOpenTypeEvent = (evt) => { const handleEvent = getOpenTypeEvent(openType); if (!handleEvent) return; if (openType === 'share') { const currentPage = getCurrentPage(pageId); const event = { from: 'button', target: getCustomEvent('tap', evt, { layoutRef }, props).target, webViewUrl: currentPage?.__webViewUrl }; if (currentPage) { const defaultMessage = { title: global.__mpx.config.rnConfig.projectName || 'AwesomeProject', path: currentPage.route || '' }; if (currentPage.onShareAppMessage) { const { promise, ...message } = currentPage.onShareAppMessage(event) || {}; if (promise) { Promise.race([Promise.resolve(promise), timer(message)]) .then((msg) => { handleEvent(Object.assign({}, defaultMessage, msg)); }); } else { handleEvent(Object.assign({}, defaultMessage, message)); } } else { handleEvent(defaultMessage); } } else { warn('Current page not found'); // Todo handleEvent(event) } } if (openType === 'getUserInfo' && bindgetuserinfo) { Promise.resolve(handleEvent) .then((userInfo) => { if (typeof userInfo === 'object') { bindgetuserinfo(userInfo); } }); } }; const handleFormTypeFn = () => { if (formType === 'submit') { submitFn && submitFn(); } else if (formType === 'reset') { resetFn && resetFn(); } }; const onTap = (evt) => { if (disabled) return; bindtap && bindtap(getCustomEvent('tap', evt, { layoutRef }, props)); handleOpenTypeEvent(evt); handleFormTypeFn(); }; const innerProps = useInnerProps(extendObject({}, props, layoutProps, { ref: nodeRef, style: extendObject({}, innerStyle, layoutStyle), bindtap: !disabled && onTap }), [ 'disabled', 'size', 'type', 'plain', 'loading', 'hover-class', 'hover-style', 'hover-start-time', 'hover-stay-time', 'open-type', 'form-type' ], { layoutRef, disableTap: disabled }); const baseButton = createElement(View, innerProps, loading && createElement(Loading, { alone: !children }), wrapChildren(props, { hasVarDec, varContext: varContextRef.current, textStyle, textProps })); const finalComponent = enableHover ? createElement(GestureDetector, { gesture: gesture }, baseButton) : baseButton; if (hasPositionFixed) { return createElement(Portal, null, finalComponent); } return finalComponent; }); Button.displayName = 'MpxButton'; export default Button;