@mpxjs/webpack-plugin
Version:
mpx compile core
276 lines (275 loc) • 13 kB
JSX
/**
* ✔ 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;