@mpxjs/webpack-plugin 2.10.3-beta.17 → 2.10.3-beta.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/runtime/components/react/dist/context.js +5 -1
- package/lib/runtime/components/react/dist/event.config.js +0 -1
- package/lib/runtime/components/react/dist/getInnerListeners.js +148 -149
- package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +145 -0
- package/lib/runtime/components/react/dist/mpx-button.jsx +11 -7
- package/lib/runtime/components/react/dist/mpx-canvas/Image.js +2 -4
- package/lib/runtime/components/react/dist/mpx-canvas/index.jsx +23 -21
- package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +9 -4
- package/lib/runtime/components/react/dist/mpx-checkbox.jsx +9 -5
- package/lib/runtime/components/react/dist/mpx-form.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-icon/icons/cancel.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/icons/clear.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/icons/download.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/icons/info.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/icons/search.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/icons/success.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/icons/success_no_circle.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/icons/waiting.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/icons/warn.png +0 -0
- package/lib/runtime/components/react/dist/mpx-icon/index.jsx +9 -4
- package/lib/runtime/components/react/dist/mpx-image.jsx +92 -41
- package/lib/runtime/components/react/dist/mpx-inline-text.jsx +11 -0
- package/lib/runtime/components/react/dist/mpx-input.jsx +14 -13
- package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +22 -7
- package/lib/runtime/components/react/dist/mpx-label.jsx +9 -5
- package/lib/runtime/components/react/dist/mpx-movable-area.jsx +10 -5
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +206 -80
- package/lib/runtime/components/react/dist/mpx-navigator.jsx +11 -3
- package/lib/runtime/components/react/dist/mpx-picker/date.jsx +194 -68
- package/lib/runtime/components/react/dist/mpx-picker/dateData.js +17 -0
- package/lib/runtime/components/react/dist/mpx-picker/index.jsx +178 -98
- package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +79 -139
- package/lib/runtime/components/react/dist/mpx-picker/region.jsx +190 -90
- package/lib/runtime/components/react/dist/mpx-picker/selector.jsx +60 -75
- package/lib/runtime/components/react/dist/mpx-picker/time.jsx +100 -228
- package/lib/runtime/components/react/dist/{mpx-picker-view.jsx → mpx-picker-view/index.jsx} +16 -15
- package/lib/runtime/components/react/dist/{mpx-picker-view-column.jsx → mpx-picker-view-column/index.jsx} +95 -26
- package/lib/runtime/components/react/dist/{mpx-picker-view-column-item.jsx → mpx-picker-view-column/pickerViewColumnItem.jsx} +16 -16
- package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItemLite.jsx +20 -0
- package/lib/runtime/components/react/dist/{pickerFaces.js → mpx-picker-view-column/pickerViewFaces.js} +6 -0
- package/lib/runtime/components/react/dist/mpx-popup/index.jsx +61 -0
- package/lib/runtime/components/react/dist/mpx-popup/popupBase.jsx +92 -0
- package/lib/runtime/components/react/dist/mpx-portal/index.jsx +5 -1
- package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +3 -5
- package/lib/runtime/components/react/dist/mpx-progress.jsx +163 -0
- package/lib/runtime/components/react/dist/mpx-radio-group.jsx +11 -4
- package/lib/runtime/components/react/dist/mpx-radio.jsx +9 -5
- package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +12 -4
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +317 -89
- package/lib/runtime/components/react/dist/mpx-simple-text.jsx +7 -5
- package/lib/runtime/components/react/dist/mpx-simple-view.jsx +11 -15
- package/lib/runtime/components/react/dist/mpx-slider.jsx +321 -0
- package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +117 -0
- package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +45 -0
- package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +15 -14
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +245 -121
- package/lib/runtime/components/react/dist/mpx-switch.jsx +10 -7
- package/lib/runtime/components/react/dist/mpx-text.jsx +43 -13
- package/lib/runtime/components/react/dist/mpx-video.jsx +12 -7
- package/lib/runtime/components/react/dist/mpx-view.jsx +34 -18
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +40 -35
- package/lib/runtime/components/react/dist/useAnimationHooks.js +35 -90
- package/lib/runtime/components/react/dist/utils.jsx +215 -109
- package/lib/runtime/components/web/mpx-titlebar.vue +21 -18
- package/package.json +1 -1
- /package/lib/runtime/components/react/dist/{pickerVIewContext.js → mpx-picker-view/pickerVIewContext.js} +0 -0
- /package/lib/runtime/components/react/dist/{pickerViewIndicator.jsx → mpx-picker-view-column/pickerViewIndicator.jsx} +0 -0
- /package/lib/runtime/components/react/dist/{pickerViewMask.jsx → mpx-picker-view-column/pickerViewMask.jsx} +0 -0
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import Reanimated, { Extrapolation, interpolate, useAnimatedStyle
|
|
3
|
-
import { extendObject } from '
|
|
4
|
-
import { createFaces } from './
|
|
5
|
-
import { usePickerViewColumnAnimationContext, usePickerViewStyleContext } from '
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import Reanimated, { Extrapolation, interpolate, useAnimatedStyle } from 'react-native-reanimated';
|
|
3
|
+
import { extendObject } from '../utils';
|
|
4
|
+
import { createFaces } from './pickerViewFaces';
|
|
5
|
+
import { usePickerViewColumnAnimationContext, usePickerViewStyleContext } from '../mpx-picker-view/pickerVIewContext';
|
|
6
6
|
const PickerViewColumnItem = ({ item, index, itemHeight, itemWidth = '100%', textStyle, textProps, visibleCount, onItemLayout }) => {
|
|
7
7
|
const textStyleFromAncestor = usePickerViewStyleContext();
|
|
8
8
|
const offsetYShared = usePickerViewColumnAnimationContext();
|
|
9
|
-
const facesShared =
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
facesShared.value = createFaces(itemHeight, visibleCount);
|
|
12
|
-
}, [itemHeight]);
|
|
9
|
+
const facesShared = useMemo(() => createFaces(itemHeight, visibleCount), [itemHeight, visibleCount]);
|
|
13
10
|
const animatedStyles = useAnimatedStyle(() => {
|
|
14
|
-
const inputRange = facesShared.
|
|
11
|
+
const inputRange = facesShared.map((f) => itemHeight * (index + f.index));
|
|
15
12
|
return {
|
|
16
|
-
opacity: interpolate(offsetYShared.value, inputRange, facesShared.
|
|
13
|
+
opacity: interpolate(offsetYShared.value, inputRange, facesShared.map((x) => x.opacity), Extrapolation.CLAMP),
|
|
17
14
|
transform: [
|
|
18
|
-
{ translateY: interpolate(offsetYShared.value, inputRange, facesShared.
|
|
19
|
-
{ rotateX: interpolate(offsetYShared.value, inputRange, facesShared.
|
|
20
|
-
{ scale: interpolate(offsetYShared.value, inputRange, facesShared.
|
|
15
|
+
{ translateY: interpolate(offsetYShared.value, inputRange, facesShared.map((x) => x.offsetY), Extrapolation.EXTEND) },
|
|
16
|
+
{ rotateX: interpolate(offsetYShared.value, inputRange, facesShared.map((x) => x.deg), Extrapolation.CLAMP) + 'deg' },
|
|
17
|
+
{ scale: interpolate(offsetYShared.value, inputRange, facesShared.map((x) => x.scale), Extrapolation.EXTEND) }
|
|
21
18
|
]
|
|
22
19
|
};
|
|
23
20
|
});
|
|
@@ -27,8 +24,11 @@ const PickerViewColumnItem = ({ item, index, itemHeight, itemWidth = '100%', tex
|
|
|
27
24
|
style: extendObject({ height: itemHeight, width: '100%' }, textStyleFromAncestor, textStyle, item.props.style)
|
|
28
25
|
}, textProps, restProps);
|
|
29
26
|
const realItem = React.cloneElement(item, itemProps);
|
|
30
|
-
return (<Reanimated.View key={strKey} style={[
|
|
31
|
-
|
|
27
|
+
return (<Reanimated.View key={strKey} style={[
|
|
28
|
+
{ height: itemHeight, width: itemWidth, pointerEvents: 'none' },
|
|
29
|
+
animatedStyles
|
|
30
|
+
]}>
|
|
31
|
+
{realItem}
|
|
32
32
|
</Reanimated.View>);
|
|
33
33
|
};
|
|
34
34
|
PickerViewColumnItem.displayName = 'MpxPickerViewColumnItem';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { extendObject } from '../utils';
|
|
4
|
+
import { usePickerViewStyleContext } from '../mpx-picker-view/pickerVIewContext';
|
|
5
|
+
const PickerViewColumnItem = ({ item, index, itemHeight, itemWidth = '100%', textStyle, textProps, onItemLayout }) => {
|
|
6
|
+
const textStyleFromAncestor = usePickerViewStyleContext();
|
|
7
|
+
const strKey = `picker-column-item-${index}`;
|
|
8
|
+
const restProps = index === 0 ? { onLayout: onItemLayout } : {};
|
|
9
|
+
const itemProps = extendObject({
|
|
10
|
+
style: extendObject({ height: itemHeight, width: '100%' }, textStyleFromAncestor, textStyle, item.props.style)
|
|
11
|
+
}, textProps, restProps);
|
|
12
|
+
const realItem = React.cloneElement(item, itemProps);
|
|
13
|
+
return (<View key={strKey} style={[
|
|
14
|
+
{ height: itemHeight, width: itemWidth, pointerEvents: 'none' }
|
|
15
|
+
]}>
|
|
16
|
+
{realItem}
|
|
17
|
+
</View>);
|
|
18
|
+
};
|
|
19
|
+
PickerViewColumnItem.displayName = 'MpxPickerViewColumnItem';
|
|
20
|
+
export default PickerViewColumnItem;
|
|
@@ -11,6 +11,12 @@ export const calcPickerHeight = (faces, itemHeight) => {
|
|
|
11
11
|
}
|
|
12
12
|
return faces.reduce((r, v) => r + calcHeight(Math.abs(v.deg), itemHeight), 0);
|
|
13
13
|
};
|
|
14
|
+
export const calcHeightOffsets = (itemHeight) => {
|
|
15
|
+
const h1 = itemHeight / 2;
|
|
16
|
+
const h2 = h1 + calcHeight(30, itemHeight);
|
|
17
|
+
const h3 = h2 + calcHeight(60, itemHeight);
|
|
18
|
+
return [h1, h2, h3];
|
|
19
|
+
};
|
|
14
20
|
export const createFaces = (itemHeight, visibleCount) => {
|
|
15
21
|
// e.g [30, 60, 90]
|
|
16
22
|
const getDegreesRelativeCenter = () => {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { cloneElement } from 'react';
|
|
2
|
+
import Portal from '../mpx-portal';
|
|
3
|
+
import PopupBase from './popupBase';
|
|
4
|
+
/**
|
|
5
|
+
* 根据 type 返回对应的弹窗壳子组件
|
|
6
|
+
*/
|
|
7
|
+
const getPopup = (type) => {
|
|
8
|
+
switch (type) {
|
|
9
|
+
case "picker" /* PopupType.PICKER */:
|
|
10
|
+
default:
|
|
11
|
+
return PopupBase;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* 基于 Portal 封装的 Popup 弹窗组件管理 Hooks
|
|
16
|
+
*/
|
|
17
|
+
const createPopupManager = (options = {}) => {
|
|
18
|
+
const { modal, type } = options;
|
|
19
|
+
const Modal = modal || getPopup(type);
|
|
20
|
+
let popupKey = null;
|
|
21
|
+
let isOpen = false;
|
|
22
|
+
let child = null;
|
|
23
|
+
const remove = () => {
|
|
24
|
+
if (popupKey !== null) {
|
|
25
|
+
Portal.remove(popupKey);
|
|
26
|
+
popupKey = null;
|
|
27
|
+
}
|
|
28
|
+
isOpen = false;
|
|
29
|
+
};
|
|
30
|
+
const open = (childComponent, pageId, options) => {
|
|
31
|
+
if (!isOpen && pageId != null) {
|
|
32
|
+
isOpen = true;
|
|
33
|
+
child = (<Modal hide={hide} {...options} visible={false}>
|
|
34
|
+
{childComponent}
|
|
35
|
+
</Modal>);
|
|
36
|
+
popupKey = Portal.add(child, pageId);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const update = (updatedChild) => {
|
|
40
|
+
if (popupKey !== null && child !== null && updatedChild !== null) {
|
|
41
|
+
child = cloneElement(child, { children: updatedChild });
|
|
42
|
+
Portal.update(popupKey, child);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const _updateVisible = (visible) => {
|
|
46
|
+
if (popupKey !== null && child !== null) {
|
|
47
|
+
child = cloneElement(child, { visible });
|
|
48
|
+
Portal.update(popupKey, child);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const show = () => _updateVisible(true);
|
|
52
|
+
const hide = () => _updateVisible(false);
|
|
53
|
+
return {
|
|
54
|
+
open,
|
|
55
|
+
show,
|
|
56
|
+
hide,
|
|
57
|
+
update,
|
|
58
|
+
remove
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
export { createPopupManager };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
|
|
3
|
+
import { getWindowInfo } from '@mpxjs/api-proxy';
|
|
4
|
+
import { useUpdateEffect } from '../utils';
|
|
5
|
+
const windowInfo = getWindowInfo();
|
|
6
|
+
const bottom = windowInfo.screenHeight - windowInfo.safeArea.bottom;
|
|
7
|
+
const styles = StyleSheet.create({
|
|
8
|
+
mask: {
|
|
9
|
+
left: 0,
|
|
10
|
+
top: 0,
|
|
11
|
+
bottom: 0,
|
|
12
|
+
right: 0,
|
|
13
|
+
backgroundColor: 'rgba(0,0,0,0.6)',
|
|
14
|
+
position: 'absolute',
|
|
15
|
+
zIndex: 1000
|
|
16
|
+
},
|
|
17
|
+
content: {
|
|
18
|
+
backgroundColor: '#ffffff',
|
|
19
|
+
borderTopLeftRadius: 10,
|
|
20
|
+
borderTopRightRadius: 10,
|
|
21
|
+
position: 'absolute',
|
|
22
|
+
bottom: 0,
|
|
23
|
+
left: 0,
|
|
24
|
+
right: 0,
|
|
25
|
+
paddingBottom: bottom
|
|
26
|
+
},
|
|
27
|
+
buttonStyle: {
|
|
28
|
+
fontSize: 18,
|
|
29
|
+
paddingTop: 10,
|
|
30
|
+
paddingBottom: 10
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const MASK_ON = 1;
|
|
34
|
+
const MASK_OFF = 0;
|
|
35
|
+
const MOVEOUT_HEIGHT = 330;
|
|
36
|
+
/**
|
|
37
|
+
* 类似微信 picker 弹窗的动画效果都可以复用此类容器
|
|
38
|
+
* 其他特定类型的弹窗容器组件可以在此基础上封装,或者扩展实现
|
|
39
|
+
*/
|
|
40
|
+
const PopupBase = (props = {}) => {
|
|
41
|
+
const { children, hide = () => null, contentHeight = MOVEOUT_HEIGHT, visible = false } = props;
|
|
42
|
+
const fade = useSharedValue(MASK_OFF);
|
|
43
|
+
const slide = useSharedValue(contentHeight);
|
|
44
|
+
const animatedStylesMask = useAnimatedStyle(() => ({
|
|
45
|
+
opacity: fade.value
|
|
46
|
+
}));
|
|
47
|
+
const animatedStylesContent = useAnimatedStyle(() => ({
|
|
48
|
+
transform: [{ translateY: slide.value }]
|
|
49
|
+
}));
|
|
50
|
+
const showAimation = () => {
|
|
51
|
+
fade.value = withTiming(MASK_ON, {
|
|
52
|
+
easing: Easing.inOut(Easing.poly(3)),
|
|
53
|
+
duration: 300
|
|
54
|
+
});
|
|
55
|
+
slide.value = withTiming(0, {
|
|
56
|
+
easing: Easing.out(Easing.poly(3)),
|
|
57
|
+
duration: 300
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const hideAnimation = () => {
|
|
61
|
+
fade.value = withTiming(MASK_OFF, {
|
|
62
|
+
easing: Easing.inOut(Easing.poly(3)),
|
|
63
|
+
duration: 300
|
|
64
|
+
});
|
|
65
|
+
slide.value = withTiming(contentHeight, {
|
|
66
|
+
easing: Easing.inOut(Easing.poly(3)),
|
|
67
|
+
duration: 300
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
useUpdateEffect(() => {
|
|
71
|
+
if (visible) {
|
|
72
|
+
showAimation();
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
hideAnimation();
|
|
76
|
+
}
|
|
77
|
+
}, [visible]);
|
|
78
|
+
const preventMaskClick = (e) => {
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
};
|
|
81
|
+
return (<Animated.View onTouchEnd={hide} style={[
|
|
82
|
+
styles.mask,
|
|
83
|
+
animatedStylesMask,
|
|
84
|
+
{ pointerEvents: visible ? 'auto' : 'none' }
|
|
85
|
+
]}>
|
|
86
|
+
<Animated.View style={[styles.content, animatedStylesContent]} onTouchEnd={preventMaskClick}>
|
|
87
|
+
{children}
|
|
88
|
+
</Animated.View>
|
|
89
|
+
</Animated.View>);
|
|
90
|
+
};
|
|
91
|
+
PopupBase.displayName = 'MpxPopupBase';
|
|
92
|
+
export default PopupBase;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { useContext, useEffect, useRef } from 'react';
|
|
2
|
-
import { PortalContext, RouteContext, VarContext } from '../context';
|
|
2
|
+
import { PortalContext, ProviderContext, RouteContext, VarContext } from '../context';
|
|
3
3
|
import PortalHost, { portal } from './portal-host';
|
|
4
4
|
const Portal = ({ children }) => {
|
|
5
5
|
const manager = useContext(PortalContext);
|
|
6
6
|
const keyRef = useRef(null);
|
|
7
7
|
const { pageId } = useContext(RouteContext) || {};
|
|
8
8
|
const varContext = useContext(VarContext);
|
|
9
|
+
const parentProvides = useContext(ProviderContext);
|
|
9
10
|
if (varContext) {
|
|
10
11
|
children = (<VarContext.Provider value={varContext} key='varContextWrap'>{children}</VarContext.Provider>);
|
|
11
12
|
}
|
|
13
|
+
if (parentProvides) {
|
|
14
|
+
children = (<ProviderContext.Provider value={parentProvides} key='providerContextWrap'>{children}</ProviderContext.Provider>);
|
|
15
|
+
}
|
|
12
16
|
useEffect(() => {
|
|
13
17
|
manager.update(keyRef.current, children);
|
|
14
18
|
}, [children]);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback, forwardRef, useImperativeHandle } from 'react';
|
|
2
|
-
import { View, StyleSheet } from 'react-native';
|
|
1
|
+
import { useState, useCallback, forwardRef, useImperativeHandle, Fragment } from 'react';
|
|
3
2
|
const _PortalManager = forwardRef((props, ref) => {
|
|
4
3
|
const [state, setState] = useState({
|
|
5
4
|
portals: []
|
|
@@ -31,10 +30,9 @@ const _PortalManager = forwardRef((props, ref) => {
|
|
|
31
30
|
portals: state.portals
|
|
32
31
|
}));
|
|
33
32
|
return (<>
|
|
34
|
-
{state.portals.map(({ key, children }
|
|
35
|
-
style={[StyleSheet.absoluteFill, { zIndex: 1000 + i, pointerEvents: 'box-none' }]}>
|
|
33
|
+
{state.portals.map(({ key, children }) => (<Fragment key={key}>
|
|
36
34
|
{children}
|
|
37
|
-
</
|
|
35
|
+
</Fragment>))}
|
|
38
36
|
</>);
|
|
39
37
|
});
|
|
40
38
|
export default _PortalManager;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ✔ percent 进度百分比 0-100
|
|
3
|
+
* ✘ show-info 在进度条右侧显示百分比
|
|
4
|
+
* ✘ border-radius 圆角大小
|
|
5
|
+
* ✘ font-size 右侧百分比字体大小
|
|
6
|
+
* ✔ stroke-width 进度条线的宽度
|
|
7
|
+
* ✔ color 进度条颜色(请使用activeColor)
|
|
8
|
+
* ✔ activeColor 已选择的进度条的颜色
|
|
9
|
+
* ✔ backgroundColor 未选择的进度条的颜色
|
|
10
|
+
* ✔ active 进度条从左往右的动画
|
|
11
|
+
* ✔ active-mode backwards: 动画从头播;forwards:动画从上次结束点接着播
|
|
12
|
+
* ✔ duration 进度增加1%所需毫秒数
|
|
13
|
+
* ✔ bindactiveend 动画完成事件
|
|
14
|
+
*/
|
|
15
|
+
import { useRef, forwardRef, useEffect, useState, createElement } from 'react';
|
|
16
|
+
import { View } from 'react-native';
|
|
17
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing, runOnJS } from 'react-native-reanimated';
|
|
18
|
+
import useInnerProps from './getInnerListeners';
|
|
19
|
+
import useNodesRef from './useNodesRef';
|
|
20
|
+
import { useLayout, useTransformStyle, extendObject, useRunOnJSCallback } from './utils';
|
|
21
|
+
import Portal from './mpx-portal';
|
|
22
|
+
const Progress = forwardRef((props, ref) => {
|
|
23
|
+
const { percent = 0, 'stroke-width': strokeWidth = 6, color, activeColor = color || '#09BB07', backgroundColor = '#EBEBEB', active = false, 'active-mode': activeMode = 'backwards', duration = 30, style = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
24
|
+
const nodeRef = useRef(null);
|
|
25
|
+
const propsRef = useRef(props);
|
|
26
|
+
propsRef.current = props;
|
|
27
|
+
// 进度值状态
|
|
28
|
+
const [lastPercent, setLastPercent] = useState(0);
|
|
29
|
+
const progressWidth = useSharedValue(0);
|
|
30
|
+
const { normalStyle, hasSelfPercent, setWidth, setHeight, hasPositionFixed } = useTransformStyle(style, {
|
|
31
|
+
enableVar,
|
|
32
|
+
externalVarContext,
|
|
33
|
+
parentFontSize,
|
|
34
|
+
parentWidth,
|
|
35
|
+
parentHeight
|
|
36
|
+
});
|
|
37
|
+
const { layoutRef, layoutStyle, layoutProps } = useLayout({
|
|
38
|
+
props,
|
|
39
|
+
hasSelfPercent,
|
|
40
|
+
setWidth,
|
|
41
|
+
setHeight,
|
|
42
|
+
nodeRef
|
|
43
|
+
});
|
|
44
|
+
useNodesRef(props, ref, nodeRef, {
|
|
45
|
+
style: normalStyle
|
|
46
|
+
});
|
|
47
|
+
// 使用 useRunOnJSCallback 处理动画回调
|
|
48
|
+
const runOnJSCallbackRef = useRef({
|
|
49
|
+
triggerActiveEnd: (percent) => {
|
|
50
|
+
const currentProps = propsRef.current;
|
|
51
|
+
if (currentProps.bindactiveend) {
|
|
52
|
+
currentProps.bindactiveend({
|
|
53
|
+
type: 'activeend',
|
|
54
|
+
detail: {
|
|
55
|
+
percent: percent
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef);
|
|
62
|
+
// 进度条动画函数
|
|
63
|
+
const startProgressAnimation = (targetPercent, startPercent, animationDuration, onFinished) => {
|
|
64
|
+
// 根据 active-mode 设置起始位置
|
|
65
|
+
progressWidth.value = startPercent;
|
|
66
|
+
progressWidth.value = withTiming(targetPercent, {
|
|
67
|
+
duration: animationDuration,
|
|
68
|
+
easing: Easing.linear
|
|
69
|
+
}, (finished) => {
|
|
70
|
+
if (finished && onFinished) {
|
|
71
|
+
// 在动画回调中,执行传入的worklet函数
|
|
72
|
+
onFinished();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
// 进度变化时的动画效果
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const targetPercent = Math.max(0, Math.min(100, percent));
|
|
79
|
+
if (active) {
|
|
80
|
+
// 根据 active-mode 确定起始位置
|
|
81
|
+
let startPercent;
|
|
82
|
+
if (activeMode === 'backwards') {
|
|
83
|
+
startPercent = 0;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// forwards 模式:使用上次记录的百分比作为起始位置
|
|
87
|
+
startPercent = lastPercent;
|
|
88
|
+
}
|
|
89
|
+
// 计算动画持续时间
|
|
90
|
+
const percentDiff = Math.abs(targetPercent - startPercent);
|
|
91
|
+
const animationDuration = percentDiff * duration;
|
|
92
|
+
// 执行动画
|
|
93
|
+
startProgressAnimation(targetPercent, startPercent, animationDuration, () => {
|
|
94
|
+
'worklet';
|
|
95
|
+
// 在worklet函数内部执行runOnJS调用runOnJSCallback
|
|
96
|
+
runOnJS(runOnJSCallback)('triggerActiveEnd', targetPercent);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
progressWidth.value = targetPercent;
|
|
101
|
+
}
|
|
102
|
+
setLastPercent(targetPercent);
|
|
103
|
+
}, [percent, active, activeMode, duration]);
|
|
104
|
+
// 初始化时设置进度值
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!active) {
|
|
107
|
+
progressWidth.value = Math.max(0, Math.min(100, percent));
|
|
108
|
+
}
|
|
109
|
+
}, []);
|
|
110
|
+
// 进度条动画样式
|
|
111
|
+
const animatedProgressStyle = useAnimatedStyle(() => {
|
|
112
|
+
return {
|
|
113
|
+
width: `${progressWidth.value}%`
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
// 确保数值类型正确
|
|
117
|
+
const strokeWidthNum = typeof strokeWidth === 'number' ? strokeWidth : parseInt(strokeWidth, 10) || 6;
|
|
118
|
+
// 容器样式
|
|
119
|
+
const containerStyle = extendObject({}, {
|
|
120
|
+
flexDirection: 'row',
|
|
121
|
+
alignItems: 'center',
|
|
122
|
+
width: '100%',
|
|
123
|
+
minHeight: Math.max(strokeWidthNum, 20)
|
|
124
|
+
}, normalStyle, layoutStyle);
|
|
125
|
+
// 进度条背景样式
|
|
126
|
+
const progressBgStyle = {
|
|
127
|
+
width: '100%',
|
|
128
|
+
height: strokeWidthNum,
|
|
129
|
+
backgroundColor,
|
|
130
|
+
overflow: 'hidden'
|
|
131
|
+
};
|
|
132
|
+
// 进度条填充样式
|
|
133
|
+
const progressFillStyle = {
|
|
134
|
+
height: '100%',
|
|
135
|
+
backgroundColor: activeColor
|
|
136
|
+
};
|
|
137
|
+
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
138
|
+
ref: nodeRef
|
|
139
|
+
}), [
|
|
140
|
+
'percent',
|
|
141
|
+
'stroke-width',
|
|
142
|
+
'color',
|
|
143
|
+
'activeColor',
|
|
144
|
+
'backgroundColor',
|
|
145
|
+
'active',
|
|
146
|
+
'active-mode',
|
|
147
|
+
'duration',
|
|
148
|
+
'bindactiveend'
|
|
149
|
+
], { layoutRef });
|
|
150
|
+
const progressComponent = createElement(View, extendObject({}, innerProps, { style: containerStyle }),
|
|
151
|
+
// 进度条背景
|
|
152
|
+
createElement(View, { style: progressBgStyle },
|
|
153
|
+
// 进度条填充
|
|
154
|
+
createElement(Animated.View, {
|
|
155
|
+
style: [progressFillStyle, animatedProgressStyle]
|
|
156
|
+
})));
|
|
157
|
+
if (hasPositionFixed) {
|
|
158
|
+
return createElement(Portal, null, progressComponent);
|
|
159
|
+
}
|
|
160
|
+
return progressComponent;
|
|
161
|
+
});
|
|
162
|
+
Progress.displayName = 'MpxProgress';
|
|
163
|
+
export default Progress;
|
|
@@ -8,6 +8,7 @@ import { FormContext, RadioGroupContext } from './context';
|
|
|
8
8
|
import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
9
9
|
import useNodesRef from './useNodesRef';
|
|
10
10
|
import { useLayout, useTransformStyle, wrapChildren, extendObject } from './utils';
|
|
11
|
+
import Portal from './mpx-portal';
|
|
11
12
|
const radioGroup = forwardRef((props, ref) => {
|
|
12
13
|
const { style = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
13
14
|
const propsRef = useRef({});
|
|
@@ -23,7 +24,7 @@ const radioGroup = forwardRef((props, ref) => {
|
|
|
23
24
|
flexWrap: 'wrap'
|
|
24
25
|
};
|
|
25
26
|
const styleObj = extendObject({}, defaultStyle, style);
|
|
26
|
-
const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
27
|
+
const { hasPositionFixed, hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
27
28
|
const nodeRef = useRef(null);
|
|
28
29
|
useNodesRef(props, ref, nodeRef, { style: normalStyle });
|
|
29
30
|
const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef });
|
|
@@ -71,16 +72,22 @@ const radioGroup = forwardRef((props, ref) => {
|
|
|
71
72
|
notifyChange
|
|
72
73
|
};
|
|
73
74
|
}, []);
|
|
74
|
-
const innerProps = useInnerProps(props,
|
|
75
|
+
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
75
76
|
ref: nodeRef,
|
|
76
77
|
style: extendObject({}, normalStyle, layoutStyle)
|
|
77
|
-
}
|
|
78
|
+
}), ['name'], {
|
|
78
79
|
layoutRef
|
|
79
80
|
});
|
|
80
|
-
|
|
81
|
+
const finalComponent = createElement(View, innerProps, createElement(RadioGroupContext.Provider, {
|
|
82
|
+
value: contextValue
|
|
83
|
+
}, wrapChildren(props, {
|
|
81
84
|
hasVarDec,
|
|
82
85
|
varContext: varContextRef.current
|
|
83
86
|
})));
|
|
87
|
+
if (hasPositionFixed) {
|
|
88
|
+
return createElement(Portal, null, finalComponent);
|
|
89
|
+
}
|
|
90
|
+
return finalComponent;
|
|
84
91
|
});
|
|
85
92
|
radioGroup.displayName = 'MpxRadioGroup';
|
|
86
93
|
export default radioGroup;
|
|
@@ -12,6 +12,7 @@ import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
|
12
12
|
import useNodesRef from './useNodesRef';
|
|
13
13
|
import { splitProps, splitStyle, useLayout, useTransformStyle, wrapChildren, extendObject } from './utils';
|
|
14
14
|
import Icon from './mpx-icon';
|
|
15
|
+
import Portal from './mpx-portal';
|
|
15
16
|
const styles = StyleSheet.create({
|
|
16
17
|
container: {
|
|
17
18
|
flexDirection: 'row',
|
|
@@ -73,7 +74,7 @@ const Radio = forwardRef((radioProps, ref) => {
|
|
|
73
74
|
bindtap && bindtap(getCustomEvent('tap', evt, { layoutRef }, props));
|
|
74
75
|
onChange(evt);
|
|
75
76
|
};
|
|
76
|
-
const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
77
|
+
const { hasPositionFixed, hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
|
|
77
78
|
const { textStyle, backgroundStyle, innerStyle = {} } = splitStyle(normalStyle);
|
|
78
79
|
if (backgroundStyle) {
|
|
79
80
|
warn('Radio does not support background image-related styles!');
|
|
@@ -91,10 +92,9 @@ const Radio = forwardRef((radioProps, ref) => {
|
|
|
91
92
|
if (labelContext) {
|
|
92
93
|
labelContext.current.triggerChange = onChange;
|
|
93
94
|
}
|
|
94
|
-
const innerProps = useInnerProps(props,
|
|
95
|
+
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
95
96
|
ref: nodeRef,
|
|
96
|
-
style: extendObject({}, innerStyle, layoutStyle)
|
|
97
|
-
}, layoutProps, {
|
|
97
|
+
style: extendObject({}, innerStyle, layoutStyle),
|
|
98
98
|
bindtap: !disabled && onTap
|
|
99
99
|
}), [
|
|
100
100
|
'value',
|
|
@@ -124,7 +124,7 @@ const Radio = forwardRef((radioProps, ref) => {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
}, [checked]);
|
|
127
|
-
|
|
127
|
+
const finalComponent = createElement(View, innerProps, createElement(View, { style: defaultStyle }, createElement(Icon, {
|
|
128
128
|
type: 'success',
|
|
129
129
|
size: 24,
|
|
130
130
|
color: disabled ? '#E1E1E1' : color,
|
|
@@ -135,6 +135,10 @@ const Radio = forwardRef((radioProps, ref) => {
|
|
|
135
135
|
textStyle,
|
|
136
136
|
textProps
|
|
137
137
|
}));
|
|
138
|
+
if (hasPositionFixed) {
|
|
139
|
+
return createElement(Portal, null, finalComponent);
|
|
140
|
+
}
|
|
141
|
+
return finalComponent;
|
|
138
142
|
});
|
|
139
143
|
Radio.displayName = 'MpxRadio';
|
|
140
144
|
export default Radio;
|
|
@@ -8,6 +8,7 @@ import useNodesRef from '../useNodesRef'; // 引入辅助函数
|
|
|
8
8
|
import { useTransformStyle, useLayout, extendObject } from '../utils';
|
|
9
9
|
import { WebView } from 'react-native-webview';
|
|
10
10
|
import { generateHTML } from './html';
|
|
11
|
+
import Portal from '../mpx-portal';
|
|
11
12
|
function jsonToHtmlStr(elements) {
|
|
12
13
|
let htmlStr = '';
|
|
13
14
|
for (const element of elements) {
|
|
@@ -30,7 +31,7 @@ const _RichText = forwardRef((props, ref) => {
|
|
|
30
31
|
const { style = {}, nodes, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
|
|
31
32
|
const nodeRef = useRef(null);
|
|
32
33
|
const [webViewHeight, setWebViewHeight] = useState(0);
|
|
33
|
-
const { normalStyle, hasSelfPercent, setWidth, setHeight } = useTransformStyle(Object.assign({
|
|
34
|
+
const { normalStyle, hasSelfPercent, setWidth, setHeight, hasPositionFixed } = useTransformStyle(Object.assign({
|
|
34
35
|
width: '100%',
|
|
35
36
|
height: webViewHeight
|
|
36
37
|
}, style), {
|
|
@@ -44,19 +45,26 @@ const _RichText = forwardRef((props, ref) => {
|
|
|
44
45
|
useNodesRef(props, ref, nodeRef, {
|
|
45
46
|
layoutRef
|
|
46
47
|
});
|
|
47
|
-
const innerProps = useInnerProps(props,
|
|
48
|
+
const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
|
|
48
49
|
ref: nodeRef,
|
|
49
50
|
style: extendObject(normalStyle, layoutStyle)
|
|
50
|
-
}
|
|
51
|
+
}), [], {
|
|
51
52
|
layoutRef
|
|
52
53
|
});
|
|
53
54
|
const html = typeof nodes === 'string' ? nodes : jsonToHtmlStr(nodes);
|
|
54
|
-
|
|
55
|
+
let finalComponent = createElement(View, innerProps, createElement(WebView, {
|
|
55
56
|
source: { html: generateHTML(html) },
|
|
56
57
|
onMessage: (event) => {
|
|
57
58
|
setWebViewHeight(+event.nativeEvent.data);
|
|
59
|
+
},
|
|
60
|
+
style: {
|
|
61
|
+
backgroundColor: 'transparent'
|
|
58
62
|
}
|
|
59
63
|
}));
|
|
64
|
+
if (hasPositionFixed) {
|
|
65
|
+
finalComponent = createElement(Portal, null, finalComponent);
|
|
66
|
+
}
|
|
67
|
+
return finalComponent;
|
|
60
68
|
});
|
|
61
69
|
_RichText.displayName = 'mpx-rich-text';
|
|
62
70
|
export default _RichText;
|