@mpxjs/webpack-plugin 2.9.55 → 2.9.57
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/index.js +2 -0
- package/lib/json-compiler/index.js +2 -1
- package/lib/platform/json/wx/index.js +0 -1
- package/lib/runtime/base.styl +27 -0
- package/lib/runtime/components/react/dist/event.config.js +27 -0
- package/lib/runtime/components/react/dist/getInnerListeners.js +230 -0
- package/lib/runtime/components/react/dist/mpx-button.jsx +270 -0
- package/lib/runtime/components/react/dist/mpx-image/index.jsx +229 -0
- package/lib/runtime/components/react/dist/mpx-image/svg.jsx +6 -0
- package/lib/runtime/components/react/dist/mpx-input.jsx +203 -0
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +294 -0
- package/lib/runtime/components/react/dist/mpx-swiper/carouse.jsx +353 -0
- package/lib/runtime/components/react/dist/mpx-swiper/index.jsx +57 -0
- package/lib/runtime/components/react/dist/mpx-swiper/type.js +1 -0
- package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +25 -0
- package/lib/runtime/components/react/dist/mpx-text.jsx +67 -0
- package/lib/runtime/components/react/dist/mpx-textarea.jsx +27 -0
- package/lib/runtime/components/react/dist/mpx-view.jsx +307 -0
- package/lib/runtime/components/react/dist/types/getInnerListeners.js +1 -0
- package/lib/runtime/components/react/dist/useNodesRef.js +25 -0
- package/lib/runtime/components/react/dist/utils.js +80 -0
- package/lib/runtime/components/react/getInnerListeners.ts +1 -1
- package/lib/runtime/components/react/mpx-button.tsx +1 -2
- package/lib/runtime/components/react/mpx-image/svg.tsx +0 -1
- package/lib/runtime/components/react/{getInnerListeners.type.ts → types/getInnerListeners.ts} +2 -2
- package/lib/runtime/components/react/types/global.d.ts +15 -0
- package/lib/runtime/optionProcessor.js +27 -1
- package/lib/template-compiler/compiler.js +72 -25
- package/lib/template-compiler/index.js +2 -1
- package/lib/web/processTemplate.js +1 -1
- package/package.json +7 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { forwardRef, useRef } from 'react';
|
|
2
|
+
import { default as Carouse } from './carouse';
|
|
3
|
+
import useInnerProps from '../getInnerListeners';
|
|
4
|
+
import useNodesRef from '../useNodesRef'; // 引入辅助函数
|
|
5
|
+
/**
|
|
6
|
+
* ✔ indicator-dots
|
|
7
|
+
* ✔ indicator-color
|
|
8
|
+
* ✔ indicator-active-color
|
|
9
|
+
* ✔ autoplay
|
|
10
|
+
* ✔ current
|
|
11
|
+
* ✔ interval
|
|
12
|
+
* ✔ duration
|
|
13
|
+
* ✔ circular
|
|
14
|
+
* ✔ vertical
|
|
15
|
+
* ✘ display-multiple-items
|
|
16
|
+
* ✔ previous-margin
|
|
17
|
+
* ✔ next-margin
|
|
18
|
+
* ✘ snap-to-edge
|
|
19
|
+
*/
|
|
20
|
+
const _SwiperWrapper = forwardRef((props, ref) => {
|
|
21
|
+
const { children } = props;
|
|
22
|
+
let innerLayout = useRef({});
|
|
23
|
+
const swiperProp = {
|
|
24
|
+
circular: props.circular || false,
|
|
25
|
+
current: props.current || 0,
|
|
26
|
+
autoplay: props.autoplay || false,
|
|
27
|
+
duration: props.duration || 500,
|
|
28
|
+
interval: props.interval || 5000,
|
|
29
|
+
showsPagination: props['indicator-dots'],
|
|
30
|
+
dotColor: props['indicator-color'] || "rgba(0, 0, 0, .3)",
|
|
31
|
+
activeDotColor: props['indicator-active-color'] || '#000000',
|
|
32
|
+
horizontal: props.vertical !== undefined ? !props.vertical : true,
|
|
33
|
+
style: props.style,
|
|
34
|
+
previousMargin: props['previous-margin'] ? parseInt(props['previous-margin']) : 0,
|
|
35
|
+
nextMargin: props['next-margin'] ? parseInt(props['next-margin']) : 0,
|
|
36
|
+
enableOffset: props['enable-offset'] || false,
|
|
37
|
+
bindchange: props.bindchange
|
|
38
|
+
};
|
|
39
|
+
const { nodeRef } = useNodesRef(props, ref, {});
|
|
40
|
+
const innerProps = useInnerProps(props, {
|
|
41
|
+
ref: nodeRef
|
|
42
|
+
}, [
|
|
43
|
+
'indicator-dots',
|
|
44
|
+
'indicator-color',
|
|
45
|
+
'indicator-active-color',
|
|
46
|
+
'previous-margin',
|
|
47
|
+
'next-margin'
|
|
48
|
+
], { layoutRef: innerLayout });
|
|
49
|
+
const getInnerLayout = (layout) => {
|
|
50
|
+
innerLayout.current = layout.current;
|
|
51
|
+
};
|
|
52
|
+
return <Carouse getInnerLayout={getInnerLayout} innerProps={innerProps} {...swiperProp} {...innerProps}>
|
|
53
|
+
{children}
|
|
54
|
+
</Carouse>;
|
|
55
|
+
});
|
|
56
|
+
_SwiperWrapper.displayName = 'mpx-swiper';
|
|
57
|
+
export default _SwiperWrapper;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import React, { forwardRef, useRef } from 'react';
|
|
3
|
+
import useInnerProps from './getInnerListeners';
|
|
4
|
+
import useNodesRef from './useNodesRef'; // 引入辅助函数
|
|
5
|
+
const _SwiperItem = forwardRef((props, ref) => {
|
|
6
|
+
const { children, 'enable-offset': enableOffset } = props;
|
|
7
|
+
const layoutRef = useRef({});
|
|
8
|
+
const { nodeRef } = useNodesRef(props, ref, {});
|
|
9
|
+
const onLayout = () => {
|
|
10
|
+
nodeRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => {
|
|
11
|
+
layoutRef.current = { x, y, width, height, offsetLeft, offsetTop };
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
const innerProps = useInnerProps(props, {
|
|
15
|
+
...(enableOffset ? { onLayout } : {}),
|
|
16
|
+
}, [
|
|
17
|
+
'children',
|
|
18
|
+
'enable-offset'
|
|
19
|
+
], { layoutRef });
|
|
20
|
+
return (<View ref={nodeRef} data-itemId={props['item-id']} {...innerProps}>
|
|
21
|
+
{children}
|
|
22
|
+
</View>);
|
|
23
|
+
});
|
|
24
|
+
_SwiperItem.displayName = 'mpx-swiper-item';
|
|
25
|
+
export default _SwiperItem;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ✔ selectable
|
|
3
|
+
* ✘ space
|
|
4
|
+
* ✘ decode
|
|
5
|
+
*/
|
|
6
|
+
import { Text, StyleSheet } from 'react-native';
|
|
7
|
+
import { useRef, useEffect, forwardRef } from 'react';
|
|
8
|
+
import useInnerProps from './getInnerListeners';
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
import useNodesRef from './useNodesRef'; // 引入辅助函数
|
|
11
|
+
import { PERCENT_REGX } from './utils';
|
|
12
|
+
const DEFAULT_STYLE = {
|
|
13
|
+
fontSize: 16
|
|
14
|
+
};
|
|
15
|
+
const transformStyle = (styleObj) => {
|
|
16
|
+
let { lineHeight } = styleObj;
|
|
17
|
+
if (typeof lineHeight === 'string' && PERCENT_REGX.test(lineHeight)) {
|
|
18
|
+
lineHeight = (parseFloat(lineHeight) / 100) * (styleObj.fontSize || DEFAULT_STYLE.fontSize);
|
|
19
|
+
styleObj.lineHeight = lineHeight;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const _Text = forwardRef((props, ref) => {
|
|
23
|
+
const { style = [], children, selectable, 'enable-offset': enableOffset, 'user-select': userSelect, 'disable-default-style': disableDefaultStyle = false, } = props;
|
|
24
|
+
const layoutRef = useRef({});
|
|
25
|
+
const styleObj = StyleSheet.flatten(style);
|
|
26
|
+
let defaultStyle = {};
|
|
27
|
+
if (!disableDefaultStyle) {
|
|
28
|
+
defaultStyle = DEFAULT_STYLE;
|
|
29
|
+
transformStyle(styleObj);
|
|
30
|
+
}
|
|
31
|
+
const { nodeRef } = useNodesRef(props, ref, {
|
|
32
|
+
defaultStyle
|
|
33
|
+
});
|
|
34
|
+
const innerProps = useInnerProps(props, {
|
|
35
|
+
ref: nodeRef
|
|
36
|
+
}, [
|
|
37
|
+
'style',
|
|
38
|
+
'children',
|
|
39
|
+
'selectable',
|
|
40
|
+
'user-select',
|
|
41
|
+
'useInherit',
|
|
42
|
+
'enable-offset'
|
|
43
|
+
], {
|
|
44
|
+
layoutRef
|
|
45
|
+
});
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
let measureTimeout = null;
|
|
48
|
+
if (enableOffset) {
|
|
49
|
+
measureTimeout = setTimeout(() => {
|
|
50
|
+
nodeRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => {
|
|
51
|
+
layoutRef.current = { x, y, width, height, offsetLeft, offsetTop };
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
return () => {
|
|
55
|
+
if (measureTimeout) {
|
|
56
|
+
clearTimeout(measureTimeout);
|
|
57
|
+
measureTimeout = null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
return (<Text style={{ ...defaultStyle, ...styleObj }} selectable={!!selectable || !!userSelect} {...innerProps}>
|
|
63
|
+
{children}
|
|
64
|
+
</Text>);
|
|
65
|
+
});
|
|
66
|
+
_Text.displayName = 'mpx-text';
|
|
67
|
+
export default _Text;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compared with Input:
|
|
3
|
+
* Subtraction:
|
|
4
|
+
* type, password, confirm-hold
|
|
5
|
+
* Addition:
|
|
6
|
+
* - confirm-type: Not support `return`
|
|
7
|
+
* ✔ auto-height
|
|
8
|
+
* ✘ fixed
|
|
9
|
+
* ✘ show-confirm-bar
|
|
10
|
+
* ✔ bindlinechange: No `heightRpx` info.
|
|
11
|
+
*/
|
|
12
|
+
import React, { forwardRef } from 'react';
|
|
13
|
+
import { Keyboard } from 'react-native';
|
|
14
|
+
import Input from './mpx-input';
|
|
15
|
+
import { omit } from './utils';
|
|
16
|
+
const Textarea = forwardRef((props, ref) => {
|
|
17
|
+
const restProps = omit(props, [
|
|
18
|
+
'ref',
|
|
19
|
+
'type',
|
|
20
|
+
'password',
|
|
21
|
+
'multiline',
|
|
22
|
+
'confirm-hold',
|
|
23
|
+
]);
|
|
24
|
+
return (<Input ref={ref} multiline confirm-type='next' bindblur={() => Keyboard.dismiss()} {...restProps}/>);
|
|
25
|
+
});
|
|
26
|
+
Textarea.displayName = 'mpx-textarea';
|
|
27
|
+
export default Textarea;
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ✔ hover-class
|
|
3
|
+
* ✘ hover-stop-propagation
|
|
4
|
+
* ✔ hover-start-time
|
|
5
|
+
* ✔ hover-stay-time
|
|
6
|
+
*/
|
|
7
|
+
import { View, Text, StyleSheet, Image } from 'react-native';
|
|
8
|
+
import { useRef, useState, useEffect, forwardRef } from 'react';
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
import useInnerProps from './getInnerListeners';
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
import useNodesRef from './useNodesRef'; // 引入辅助函数
|
|
13
|
+
import { parseUrl, TEXT_STYLE_REGEX, PERCENT_REGX, isText } from './utils';
|
|
14
|
+
const IMAGE_STYLE_REGEX = /^background(Image|Size|Repeat|Position)$/;
|
|
15
|
+
function groupBy(style, callback, group = {}) {
|
|
16
|
+
let groupKey = '';
|
|
17
|
+
for (let key in style) {
|
|
18
|
+
if (style.hasOwnProperty(key)) { // 确保处理对象自身的属性
|
|
19
|
+
let val = style[key];
|
|
20
|
+
groupKey = callback(key, val);
|
|
21
|
+
if (!group[groupKey]) {
|
|
22
|
+
group[groupKey] = {};
|
|
23
|
+
}
|
|
24
|
+
group[groupKey][key] = val;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return group;
|
|
28
|
+
}
|
|
29
|
+
const applyHandlers = (handlers, args) => {
|
|
30
|
+
for (let handler of handlers) {
|
|
31
|
+
handler(...args);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const checkNeedLayout = (style) => {
|
|
35
|
+
const [width, height] = style.sizeList;
|
|
36
|
+
return (PERCENT_REGX.test(`${height}`) && width === 'auto') || (PERCENT_REGX.test(`${width}`) && height === 'auto');
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* h - 用户设置的高度
|
|
40
|
+
* lh - 容器的高度
|
|
41
|
+
* ratio - 原始图片的宽高比
|
|
42
|
+
* **/
|
|
43
|
+
function calculateSize(h, lh, ratio) {
|
|
44
|
+
let height, width;
|
|
45
|
+
if (PERCENT_REGX.test(`${h}`)) { // auto px/rpx
|
|
46
|
+
if (!lh)
|
|
47
|
+
return null;
|
|
48
|
+
height = (parseFloat(`${h}`) / 100) * lh;
|
|
49
|
+
width = height * ratio;
|
|
50
|
+
}
|
|
51
|
+
else { // 2. auto px/rpx - 根据比例计算
|
|
52
|
+
height = h;
|
|
53
|
+
width = height * ratio;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
width,
|
|
57
|
+
height
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// background-size 转换
|
|
61
|
+
function backgroundSize(imageProps, preImageInfo, imageSize, layoutInfo) {
|
|
62
|
+
let sizeList = preImageInfo.sizeList;
|
|
63
|
+
if (!sizeList)
|
|
64
|
+
return;
|
|
65
|
+
// 枚举值
|
|
66
|
+
if (['cover', 'contain'].includes(`${sizeList[0]}`)) {
|
|
67
|
+
imageProps.style.resizeMode = sizeList[0];
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const [width, height] = sizeList;
|
|
71
|
+
let newWidth = 0, newHeight = 0;
|
|
72
|
+
const { width: imageSizeWidth, height: imageSizeHeight } = imageSize || {};
|
|
73
|
+
if (width === 'auto' && height === 'auto') { // 均为auto
|
|
74
|
+
if (!imageSize)
|
|
75
|
+
return;
|
|
76
|
+
newHeight = imageSizeHeight;
|
|
77
|
+
newWidth = imageSizeWidth;
|
|
78
|
+
}
|
|
79
|
+
else if (width === 'auto') { // auto px/rpx/%
|
|
80
|
+
if (!imageSize)
|
|
81
|
+
return;
|
|
82
|
+
const dimensions = calculateSize(height, layoutInfo?.height, imageSizeWidth / imageSizeHeight);
|
|
83
|
+
if (!dimensions)
|
|
84
|
+
return;
|
|
85
|
+
newWidth = dimensions.width;
|
|
86
|
+
newHeight = dimensions.height;
|
|
87
|
+
}
|
|
88
|
+
else if (height === 'auto') { // auto px/rpx/%
|
|
89
|
+
if (!imageSize)
|
|
90
|
+
return;
|
|
91
|
+
const dimensions = calculateSize(width, layoutInfo?.width, imageSizeHeight / imageSizeWidth);
|
|
92
|
+
if (!dimensions)
|
|
93
|
+
return;
|
|
94
|
+
newHeight = dimensions.width;
|
|
95
|
+
newWidth = dimensions.height;
|
|
96
|
+
}
|
|
97
|
+
else { // 数值类型 ImageStyle
|
|
98
|
+
// 数值类型设置为 stretch
|
|
99
|
+
imageProps.style.resizeMode = 'stretch';
|
|
100
|
+
newWidth = PERCENT_REGX.test(`${width}`) ? width : +width;
|
|
101
|
+
newHeight = PERCENT_REGX.test(`${width}`) ? height : +height;
|
|
102
|
+
}
|
|
103
|
+
// 样式合并
|
|
104
|
+
imageProps.style = {
|
|
105
|
+
...imageProps.style,
|
|
106
|
+
width: newWidth,
|
|
107
|
+
height: newHeight
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// background-image转换为source
|
|
112
|
+
function backgroundImage(imageProps, preImageInfo) {
|
|
113
|
+
imageProps.src = preImageInfo.src;
|
|
114
|
+
}
|
|
115
|
+
const imageStyleToProps = (preImageInfo, imageSize, layoutInfo) => {
|
|
116
|
+
// 初始化
|
|
117
|
+
const imageProps = {
|
|
118
|
+
style: {
|
|
119
|
+
resizeMode: 'cover',
|
|
120
|
+
...StyleSheet.absoluteFillObject
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
applyHandlers([backgroundSize, backgroundImage], [imageProps, preImageInfo, imageSize, layoutInfo]);
|
|
124
|
+
if (!imageProps?.src)
|
|
125
|
+
return null;
|
|
126
|
+
return imageProps;
|
|
127
|
+
};
|
|
128
|
+
function preParseImage(imageStyle) {
|
|
129
|
+
const { backgroundImage, backgroundSize = ["auto"] } = imageStyle || {};
|
|
130
|
+
const src = parseUrl(backgroundImage);
|
|
131
|
+
let sizeList = backgroundSize.slice();
|
|
132
|
+
sizeList.length === 1 && sizeList.push(sizeList[0]);
|
|
133
|
+
return {
|
|
134
|
+
src,
|
|
135
|
+
sizeList
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function wrapImage(imageStyle) {
|
|
139
|
+
const [show, setShow] = useState(false);
|
|
140
|
+
const [, setImageSizeWidth] = useState(null);
|
|
141
|
+
const [, setImageSizeHeight] = useState(null);
|
|
142
|
+
const [, setLayoutInfoWidth] = useState(null);
|
|
143
|
+
const [, setLayoutInfoHeight] = useState(null);
|
|
144
|
+
const sizeInfo = useRef(null);
|
|
145
|
+
const layoutInfo = useRef(null);
|
|
146
|
+
// 预解析
|
|
147
|
+
const preImageInfo = preParseImage(imageStyle);
|
|
148
|
+
// 判断是否可挂载onLayout
|
|
149
|
+
const needLayout = checkNeedLayout(preImageInfo);
|
|
150
|
+
const { src, sizeList } = preImageInfo;
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (!src) {
|
|
153
|
+
setShow(false);
|
|
154
|
+
sizeInfo.current = null;
|
|
155
|
+
layoutInfo.current = null;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (!sizeList.includes('auto')) {
|
|
159
|
+
setShow(true);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
Image.getSize(src, (width, height) => {
|
|
163
|
+
sizeInfo.current = {
|
|
164
|
+
width,
|
|
165
|
+
height
|
|
166
|
+
};
|
|
167
|
+
//1. 当需要绑定onLayout 2. 获取到布局信息
|
|
168
|
+
if (!needLayout || layoutInfo.current) {
|
|
169
|
+
setImageSizeWidth(width);
|
|
170
|
+
setImageSizeHeight(height);
|
|
171
|
+
if (layoutInfo.current) {
|
|
172
|
+
setLayoutInfoWidth(layoutInfo.current.width);
|
|
173
|
+
setLayoutInfoHeight(layoutInfo.current.height);
|
|
174
|
+
}
|
|
175
|
+
setShow(true);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}, [preImageInfo?.src]);
|
|
179
|
+
if (!preImageInfo?.src)
|
|
180
|
+
return null;
|
|
181
|
+
const onLayout = (res) => {
|
|
182
|
+
const { width, height } = res?.nativeEvent?.layout || {};
|
|
183
|
+
layoutInfo.current = {
|
|
184
|
+
width,
|
|
185
|
+
height
|
|
186
|
+
};
|
|
187
|
+
if (sizeInfo.current) {
|
|
188
|
+
setImageSizeWidth(sizeInfo.current.width);
|
|
189
|
+
setImageSizeHeight(sizeInfo.current.height);
|
|
190
|
+
setLayoutInfoWidth(width);
|
|
191
|
+
setLayoutInfoHeight(height);
|
|
192
|
+
setShow(true);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
return <View key='viewBgImg' {...needLayout ? { onLayout } : null} style={{ ...StyleSheet.absoluteFillObject, width: '100%', height: '100%', overflow: 'hidden' }}>
|
|
196
|
+
{show && <Image {...imageStyleToProps(preImageInfo, sizeInfo.current, layoutInfo.current)}/>}
|
|
197
|
+
</View>;
|
|
198
|
+
}
|
|
199
|
+
function splitStyle(styles) {
|
|
200
|
+
return groupBy(styles, (key) => {
|
|
201
|
+
if (TEXT_STYLE_REGEX.test(key))
|
|
202
|
+
return 'textStyle';
|
|
203
|
+
else if (IMAGE_STYLE_REGEX.test(key))
|
|
204
|
+
return 'imageStyle';
|
|
205
|
+
return 'innerStyle';
|
|
206
|
+
}, {});
|
|
207
|
+
}
|
|
208
|
+
function every(children, callback) {
|
|
209
|
+
return children.every((child) => callback(child));
|
|
210
|
+
}
|
|
211
|
+
function wrapChildren(children, textStyle, imageStyle) {
|
|
212
|
+
children = Array.isArray(children) ? children : [children];
|
|
213
|
+
if (every(children, (child) => isText(child))) {
|
|
214
|
+
children = [<Text key='viewTextWrap' style={textStyle}>{children}</Text>];
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
if (textStyle)
|
|
218
|
+
console.warn('Text style will be ignored unless every child of the view is Text node!');
|
|
219
|
+
}
|
|
220
|
+
return [
|
|
221
|
+
wrapImage(imageStyle),
|
|
222
|
+
...children
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
const _View = forwardRef((props, ref) => {
|
|
226
|
+
const { style = [], children, hoverStyle, 'hover-start-time': hoverStartTime = 50, 'hover-stay-time': hoverStayTime = 400, 'enable-offset': enableOffset } = props;
|
|
227
|
+
const [isHover, setIsHover] = useState(false);
|
|
228
|
+
const layoutRef = useRef({});
|
|
229
|
+
// 打平 style 数组
|
|
230
|
+
const styleObj = StyleSheet.flatten(style);
|
|
231
|
+
// 默认样式
|
|
232
|
+
const defaultStyle = {
|
|
233
|
+
// flex 布局相关的默认样式
|
|
234
|
+
...styleObj.display === 'flex' && {
|
|
235
|
+
flexDirection: 'row',
|
|
236
|
+
flexBasis: 'auto',
|
|
237
|
+
flexShrink: 1,
|
|
238
|
+
flexWrap: 'nowrap'
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
const { nodeRef } = useNodesRef(props, ref, {
|
|
242
|
+
defaultStyle
|
|
243
|
+
});
|
|
244
|
+
const dataRef = useRef({});
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
return () => {
|
|
247
|
+
dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer);
|
|
248
|
+
dataRef.current.stayTimer && clearTimeout(dataRef.current.stayTimer);
|
|
249
|
+
};
|
|
250
|
+
}, []);
|
|
251
|
+
const setStartTimer = () => {
|
|
252
|
+
dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer);
|
|
253
|
+
dataRef.current.startTimer = setTimeout(() => {
|
|
254
|
+
setIsHover(() => true);
|
|
255
|
+
}, +hoverStartTime);
|
|
256
|
+
};
|
|
257
|
+
const setStayTimer = () => {
|
|
258
|
+
dataRef.current.stayTimer && clearTimeout(dataRef.current.stayTimer);
|
|
259
|
+
dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer);
|
|
260
|
+
dataRef.current.stayTimer = setTimeout(() => {
|
|
261
|
+
setIsHover(() => false);
|
|
262
|
+
}, +hoverStayTime);
|
|
263
|
+
};
|
|
264
|
+
function onTouchStart(e) {
|
|
265
|
+
const { bindtouchstart } = props;
|
|
266
|
+
bindtouchstart && bindtouchstart(e);
|
|
267
|
+
setStartTimer();
|
|
268
|
+
}
|
|
269
|
+
function onTouchEnd(e) {
|
|
270
|
+
const { bindtouchend } = props;
|
|
271
|
+
bindtouchend && bindtouchend(e);
|
|
272
|
+
setStayTimer();
|
|
273
|
+
}
|
|
274
|
+
const onLayout = () => {
|
|
275
|
+
nodeRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => {
|
|
276
|
+
layoutRef.current = { x, y, width, height, offsetLeft, offsetTop };
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
const { textStyle, imageStyle, innerStyle } = splitStyle(StyleSheet.flatten([
|
|
280
|
+
defaultStyle,
|
|
281
|
+
styleObj,
|
|
282
|
+
...(isHover ? hoverStyle : [])
|
|
283
|
+
]));
|
|
284
|
+
const innerProps = useInnerProps(props, {
|
|
285
|
+
ref: nodeRef,
|
|
286
|
+
...enableOffset ? { onLayout } : {},
|
|
287
|
+
...(hoverStyle && {
|
|
288
|
+
bindtouchstart: onTouchStart,
|
|
289
|
+
bindtouchend: onTouchEnd
|
|
290
|
+
})
|
|
291
|
+
}, [
|
|
292
|
+
'style',
|
|
293
|
+
'children',
|
|
294
|
+
'hover-start-time',
|
|
295
|
+
'hover-stay-time',
|
|
296
|
+
'hoverStyle',
|
|
297
|
+
'hover-class',
|
|
298
|
+
'enable-offset'
|
|
299
|
+
], {
|
|
300
|
+
layoutRef
|
|
301
|
+
});
|
|
302
|
+
return (<View {...innerProps} style={innerStyle}>
|
|
303
|
+
{wrapChildren(children, textStyle, imageStyle)}
|
|
304
|
+
</View>);
|
|
305
|
+
});
|
|
306
|
+
_View.displayName = 'mpx-view';
|
|
307
|
+
export default _View;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useRef, useEffect, useImperativeHandle } from 'react';
|
|
2
|
+
export default function useNodesRef(props, ref, instance = {}) {
|
|
3
|
+
const nodeRef = useRef(null);
|
|
4
|
+
const _props = useRef(props);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
_props.current = props;
|
|
7
|
+
return () => {
|
|
8
|
+
_props.current = null; // 组件销毁,清空 _props 依赖数据
|
|
9
|
+
};
|
|
10
|
+
}, [props]);
|
|
11
|
+
useImperativeHandle(ref, () => {
|
|
12
|
+
return {
|
|
13
|
+
getNodeInstance() {
|
|
14
|
+
return {
|
|
15
|
+
props: _props,
|
|
16
|
+
nodeRef,
|
|
17
|
+
instance
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
nodeRef
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useEffect, useRef, Children, isValidElement } from 'react';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
export const TEXT_STYLE_REGEX = /color|font.*|text.*|letterSpacing|lineHeight|includeFontPadding|writingDirection/;
|
|
4
|
+
export const PERCENT_REGX = /\d+(\.\d+)?%$/;
|
|
5
|
+
const URL_REGEX = /url\(["']?(.*?)["']?\)/;
|
|
6
|
+
export function omit(obj, fields) {
|
|
7
|
+
const shallowCopy = Object.assign({}, obj);
|
|
8
|
+
for (let i = 0; i < fields.length; i += 1) {
|
|
9
|
+
const key = fields[i];
|
|
10
|
+
delete shallowCopy[key];
|
|
11
|
+
}
|
|
12
|
+
return shallowCopy;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 从 style 中提取 TextStyle
|
|
16
|
+
* @param style
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
export const extractTextStyle = (style) => {
|
|
20
|
+
return Object.entries(StyleSheet.flatten(style)).reduce((textStyle, [key, value]) => {
|
|
21
|
+
TEXT_STYLE_REGEX.test(key) && Object.assign(textStyle, { [key]: value });
|
|
22
|
+
return textStyle;
|
|
23
|
+
}, {});
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* 用法等同于 useEffect,但是会忽略首次执行,只在依赖更新时执行
|
|
27
|
+
*/
|
|
28
|
+
export const useUpdateEffect = (effect, deps) => {
|
|
29
|
+
const isMounted = useRef(false);
|
|
30
|
+
// for react-refresh
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
return () => {
|
|
33
|
+
isMounted.current = false;
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!isMounted.current) {
|
|
38
|
+
isMounted.current = true;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return effect();
|
|
42
|
+
}
|
|
43
|
+
}, deps);
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* 解析行内样式
|
|
47
|
+
* @param inlineStyle
|
|
48
|
+
* @returns
|
|
49
|
+
*/
|
|
50
|
+
export const parseInlineStyle = (inlineStyle = '') => {
|
|
51
|
+
return inlineStyle.split(';').reduce((styleObj, style) => {
|
|
52
|
+
const [k, v, ...rest] = style.split(':');
|
|
53
|
+
if (rest.length || !v || !k)
|
|
54
|
+
return styleObj;
|
|
55
|
+
const key = k.trim().replace(/-./g, c => c.substring(1).toUpperCase());
|
|
56
|
+
return Object.assign(styleObj, { [key]: v.trim() });
|
|
57
|
+
}, {});
|
|
58
|
+
};
|
|
59
|
+
export const parseUrl = (cssUrl = '') => {
|
|
60
|
+
if (!cssUrl)
|
|
61
|
+
return;
|
|
62
|
+
const match = cssUrl.match(URL_REGEX);
|
|
63
|
+
return match?.[1];
|
|
64
|
+
};
|
|
65
|
+
export const getRestProps = (transferProps = {}, originProps = {}, deletePropsKey = []) => {
|
|
66
|
+
return {
|
|
67
|
+
...transferProps,
|
|
68
|
+
...omit(originProps, deletePropsKey)
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export const isText = (ele) => {
|
|
72
|
+
if (isValidElement(ele)) {
|
|
73
|
+
const displayName = ele.type?.displayName;
|
|
74
|
+
return displayName === 'mpx-text' || displayName === 'Text';
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
};
|
|
78
|
+
export function every(children, callback) {
|
|
79
|
+
return Children.toArray(children).every((child) => callback(child));
|
|
80
|
+
}
|
|
@@ -331,14 +331,13 @@ const Button = forwardRef<HandlerRef< View, ButtonProps>,ButtonProps >((props, r
|
|
|
331
331
|
const catchTap = (evt: NativeSyntheticEvent<TouchEvent>) => {
|
|
332
332
|
if (disabled) return
|
|
333
333
|
catchtap && catchtap(getCustomEvent('tap', evt, { layoutRef }, props))
|
|
334
|
-
handleOpenTypeEvent(evt)
|
|
335
334
|
}
|
|
336
335
|
|
|
337
336
|
function wrapChildren(children: ReactNode, textStyle?: StyleProp<TextStyle>) {
|
|
338
337
|
if (every(children, (child)=>isText(child))) {
|
|
339
338
|
children = [<Text key='buttonTextWrap' style={textStyle}>{children}</Text>]
|
|
340
339
|
} else {
|
|
341
|
-
if(textStyle) console.warn('Text style will be ignored unless every child of the
|
|
340
|
+
if(textStyle) console.warn('Text style will be ignored unless every child of the Button is Text node!')
|
|
342
341
|
}
|
|
343
342
|
|
|
344
343
|
return children
|
package/lib/runtime/components/react/{getInnerListeners.type.ts → types/getInnerListeners.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { MutableRefObject } from 'react'
|
|
2
2
|
import { NativeSyntheticEvent } from 'react-native'
|
|
3
3
|
|
|
4
|
-
type LayoutRef =
|
|
4
|
+
type LayoutRef = MutableRefObject<any>
|
|
5
5
|
|
|
6
6
|
type SetTimeoutReturnType = ReturnType<typeof setTimeout>
|
|
7
7
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare module 'react-native-svg/css' {
|
|
2
|
+
import type { ImageSourcePropType, StyleProp, ImageStyle } from 'react-native'
|
|
3
|
+
import type { SvgProps as SvgCssUriProps } from 'react-native-svg'
|
|
4
|
+
|
|
5
|
+
export const SvgCssUri: React.ComponentType<SvgCssUriProps & { uri?: string }>
|
|
6
|
+
|
|
7
|
+
export interface WithLocalSvgProps {
|
|
8
|
+
asset: ImageSourcePropType
|
|
9
|
+
style?: StyleProp<ImageStyle>
|
|
10
|
+
width?: string | number
|
|
11
|
+
height?: string | number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const WithLocalSvg: React.ComponentType<WithLocalSvgProps>
|
|
15
|
+
}
|