@mpxjs/webpack-plugin 2.8.25-alpha.21 → 2.8.25-alpha.22
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/KeyboardAvoidingView.jsx +89 -0
- package/lib/runtime/components/react/dist/context.js +14 -0
- package/lib/runtime/components/react/dist/event.config.js +27 -0
- package/lib/runtime/components/react/dist/getInnerListeners.js +262 -0
- package/lib/runtime/components/react/dist/mpx-button.jsx +271 -0
- package/lib/runtime/components/react/dist/mpx-canvas/Bus.js +60 -0
- package/lib/runtime/components/react/dist/mpx-canvas/CanvasGradient.js +15 -0
- package/lib/runtime/components/react/dist/mpx-canvas/CanvasRenderingContext2D.js +84 -0
- package/lib/runtime/components/react/dist/mpx-canvas/Image.js +87 -0
- package/lib/runtime/components/react/dist/mpx-canvas/ImageData.js +15 -0
- package/lib/runtime/components/react/dist/mpx-canvas/constructorsRegistry.js +28 -0
- package/lib/runtime/components/react/dist/mpx-canvas/html.js +341 -0
- package/lib/runtime/components/react/dist/mpx-canvas/index.jsx +236 -0
- package/lib/runtime/components/react/dist/mpx-canvas/utils.jsx +89 -0
- package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +90 -0
- package/lib/runtime/components/react/dist/mpx-checkbox.jsx +131 -0
- package/lib/runtime/components/react/dist/mpx-form.jsx +68 -0
- 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 +50 -0
- package/lib/runtime/components/react/dist/mpx-image.jsx +292 -0
- package/lib/runtime/components/react/dist/mpx-input.jsx +292 -0
- package/lib/runtime/components/react/dist/mpx-label.jsx +52 -0
- package/lib/runtime/components/react/dist/mpx-movable-area.jsx +32 -0
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +468 -0
- package/lib/runtime/components/react/dist/mpx-navigator.jsx +33 -0
- package/lib/runtime/components/react/dist/mpx-picker/date.jsx +74 -0
- package/lib/runtime/components/react/dist/mpx-picker/index.jsx +141 -0
- package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +147 -0
- package/lib/runtime/components/react/dist/mpx-picker/region.jsx +99 -0
- package/lib/runtime/components/react/dist/mpx-picker/regionData.js +6099 -0
- package/lib/runtime/components/react/dist/mpx-picker/selector.jsx +81 -0
- package/lib/runtime/components/react/dist/mpx-picker/time.jsx +242 -0
- package/lib/runtime/components/react/dist/mpx-picker/type.js +1 -0
- package/lib/runtime/components/react/dist/mpx-picker-view-column-item.jsx +35 -0
- package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +193 -0
- package/lib/runtime/components/react/dist/mpx-picker-view.jsx +125 -0
- package/lib/runtime/components/react/dist/mpx-portal/index.jsx +30 -0
- package/lib/runtime/components/react/dist/mpx-portal/portal-host.jsx +112 -0
- package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +41 -0
- package/lib/runtime/components/react/dist/mpx-radio-group.jsx +86 -0
- package/lib/runtime/components/react/dist/mpx-radio.jsx +140 -0
- package/lib/runtime/components/react/dist/mpx-rich-text/html.js +39 -0
- package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +62 -0
- package/lib/runtime/components/react/dist/mpx-root-portal.jsx +17 -0
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +372 -0
- package/lib/runtime/components/react/dist/mpx-simple-text.jsx +11 -0
- package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +59 -0
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +671 -0
- package/lib/runtime/components/react/dist/mpx-switch.jsx +97 -0
- package/lib/runtime/components/react/dist/mpx-text.jsx +41 -0
- package/lib/runtime/components/react/dist/mpx-textarea.jsx +40 -0
- package/lib/runtime/components/react/dist/mpx-video.jsx +248 -0
- package/lib/runtime/components/react/dist/mpx-view.jsx +611 -0
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +289 -0
- package/lib/runtime/components/react/dist/parser.js +218 -0
- package/lib/runtime/components/react/dist/pickerFaces.js +76 -0
- package/lib/runtime/components/react/dist/pickerVIewContext.js +14 -0
- package/lib/runtime/components/react/dist/pickerViewIndicator.jsx +23 -0
- package/lib/runtime/components/react/dist/pickerViewMask.jsx +18 -0
- package/lib/runtime/components/react/dist/useAnimationHooks.js +346 -0
- package/lib/runtime/components/react/dist/useNodesRef.js +16 -0
- package/lib/runtime/components/react/dist/utils.jsx +599 -0
- package/package.json +6 -3
- package/LICENSE +0 -433
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 普通选择器,range可以是Array<Obj> 也可以是Array
|
|
3
|
+
*/
|
|
4
|
+
import { View, TouchableWithoutFeedback } from 'react-native';
|
|
5
|
+
import React, { forwardRef, useState, useRef, useEffect } from 'react';
|
|
6
|
+
import { Picker } from '@ant-design/react-native';
|
|
7
|
+
import useNodesRef from '../useNodesRef'; // 引入辅助函数
|
|
8
|
+
const formatRangeFun = (range, rangeKey = '') => {
|
|
9
|
+
let newRange = [];
|
|
10
|
+
newRange = (range || []).map((item, index) => {
|
|
11
|
+
if (typeof item === 'object') {
|
|
12
|
+
return { value: index, label: item[rangeKey] };
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return { value: index, label: item };
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
return newRange;
|
|
19
|
+
};
|
|
20
|
+
const _SelectorPicker = forwardRef((props, ref) => {
|
|
21
|
+
const { range, children, value, disabled, bindchange, bindcancel, style } = props;
|
|
22
|
+
// 格式化数据为Array<*>
|
|
23
|
+
const formatRange = formatRangeFun(range, props['range-key']);
|
|
24
|
+
// 选中的索引值
|
|
25
|
+
const [selected, setSelected] = useState(value || 0);
|
|
26
|
+
// range数据源
|
|
27
|
+
const [data, setData] = useState(formatRange || []);
|
|
28
|
+
// 存储layout布局信息
|
|
29
|
+
const layoutRef = useRef({});
|
|
30
|
+
const viewRef = useRef(null);
|
|
31
|
+
const nodeRef = useRef(null);
|
|
32
|
+
useNodesRef(props, ref, nodeRef, {
|
|
33
|
+
style
|
|
34
|
+
});
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (range) {
|
|
37
|
+
const newFormatRange = formatRangeFun(range, props['range-key']);
|
|
38
|
+
setData(newFormatRange);
|
|
39
|
+
}
|
|
40
|
+
setSelected(() => {
|
|
41
|
+
return value;
|
|
42
|
+
});
|
|
43
|
+
}, [range, value]);
|
|
44
|
+
const defaultValue = [value];
|
|
45
|
+
const onChange = (value) => {
|
|
46
|
+
bindchange && bindchange({
|
|
47
|
+
detail: {
|
|
48
|
+
value: value && value[0]
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const onElementLayout = (layout) => {
|
|
53
|
+
viewRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => {
|
|
54
|
+
layoutRef.current = { x, y, width, height, offsetLeft, offsetTop };
|
|
55
|
+
props.getInnerLayout && props.getInnerLayout(layoutRef);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const antPickerProps = {
|
|
59
|
+
ref: nodeRef,
|
|
60
|
+
data,
|
|
61
|
+
value: [selected],
|
|
62
|
+
cols: 1,
|
|
63
|
+
defaultValue,
|
|
64
|
+
itemHeight: 40,
|
|
65
|
+
onChange,
|
|
66
|
+
onDismiss: bindcancel && bindcancel
|
|
67
|
+
};
|
|
68
|
+
const touchProps = {
|
|
69
|
+
onLayout: onElementLayout,
|
|
70
|
+
ref: viewRef
|
|
71
|
+
};
|
|
72
|
+
return (<Picker {...antPickerProps}>
|
|
73
|
+
<TouchableWithoutFeedback>
|
|
74
|
+
<View {...touchProps}>
|
|
75
|
+
{children}
|
|
76
|
+
</View>
|
|
77
|
+
</TouchableWithoutFeedback>
|
|
78
|
+
</Picker>);
|
|
79
|
+
});
|
|
80
|
+
_SelectorPicker.displayName = 'mpx-picker-selector';
|
|
81
|
+
export default _SelectorPicker;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { View, Text, Modal, TouchableWithoutFeedback } from 'react-native';
|
|
2
|
+
import Portal from '../mpx-portal/index';
|
|
3
|
+
import { PickerView } from '@ant-design/react-native';
|
|
4
|
+
import React, { forwardRef, useState, useRef, useEffect } from 'react';
|
|
5
|
+
import useNodesRef from '../useNodesRef'; // 引入辅助函数
|
|
6
|
+
// 可见应用窗口的大小。
|
|
7
|
+
// const { height: dHeight, width: dWidth } = Dimensions.get('window');
|
|
8
|
+
// modal属性: {"height": 298.33331298828125, "offsetLeft": 0, "offsetTop": 513.6666870117188, "width": 375, "x": 0, "y": 513.6666870117188}
|
|
9
|
+
// const { height: sHeight, width: sWidth } = Dimensions.get('screen');
|
|
10
|
+
// 设备屏幕的大小。 screen
|
|
11
|
+
const styles = {
|
|
12
|
+
showModal: {
|
|
13
|
+
backgroundColor: 'black',
|
|
14
|
+
opacity: 0.5,
|
|
15
|
+
width: '100%',
|
|
16
|
+
height: '100%'
|
|
17
|
+
},
|
|
18
|
+
hideModal: {
|
|
19
|
+
opacity: 1,
|
|
20
|
+
height: 0
|
|
21
|
+
},
|
|
22
|
+
modal: {
|
|
23
|
+
backgroundColor: 'black',
|
|
24
|
+
opacity: 0.5
|
|
25
|
+
},
|
|
26
|
+
centeredView: {
|
|
27
|
+
position: 'absolute',
|
|
28
|
+
bottom: 0,
|
|
29
|
+
width: '100%',
|
|
30
|
+
overflow: 'scroll'
|
|
31
|
+
},
|
|
32
|
+
btnLine: {
|
|
33
|
+
width: '100%',
|
|
34
|
+
flex: 1,
|
|
35
|
+
flexDirection: 'row',
|
|
36
|
+
justifyContent: 'space-between',
|
|
37
|
+
borderColor: 20,
|
|
38
|
+
borderBottomWidth: 1,
|
|
39
|
+
backgroundColor: 'white',
|
|
40
|
+
paddingLeft: 40,
|
|
41
|
+
paddingRight: 40
|
|
42
|
+
},
|
|
43
|
+
cancel: {
|
|
44
|
+
height: 50,
|
|
45
|
+
display: 'flex',
|
|
46
|
+
justifyContent: 'center'
|
|
47
|
+
},
|
|
48
|
+
ok: {
|
|
49
|
+
height: 50,
|
|
50
|
+
display: 'flex',
|
|
51
|
+
justifyContent: 'center'
|
|
52
|
+
},
|
|
53
|
+
btntext: {
|
|
54
|
+
color: '#0ae',
|
|
55
|
+
fontSize: 18
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function formatStrToInt(timestr) {
|
|
59
|
+
const [start, end] = timestr.split(':');
|
|
60
|
+
return [parseInt(start), parseInt(end)];
|
|
61
|
+
}
|
|
62
|
+
// [9, 59] to 09:59
|
|
63
|
+
function formatStr(arr) {
|
|
64
|
+
let [hour, minute] = arr;
|
|
65
|
+
if (hour < 10) {
|
|
66
|
+
hour = '0' + hour;
|
|
67
|
+
}
|
|
68
|
+
if (minute < 10) {
|
|
69
|
+
minute = '0' + minute;
|
|
70
|
+
}
|
|
71
|
+
return hour + ':' + minute;
|
|
72
|
+
}
|
|
73
|
+
function generateMinute() {
|
|
74
|
+
const arrMinute = [];
|
|
75
|
+
for (let i = 0; i <= 59; i++) {
|
|
76
|
+
const obj = {
|
|
77
|
+
label: toSingleStr(i),
|
|
78
|
+
value: i,
|
|
79
|
+
children: []
|
|
80
|
+
};
|
|
81
|
+
arrMinute.push(obj);
|
|
82
|
+
}
|
|
83
|
+
return arrMinute;
|
|
84
|
+
}
|
|
85
|
+
function generateColumns() {
|
|
86
|
+
const pickData = [];
|
|
87
|
+
for (let i = 0; i <= 23; i++) {
|
|
88
|
+
const obj = {
|
|
89
|
+
label: toSingleStr(i),
|
|
90
|
+
value: i,
|
|
91
|
+
children: generateMinute()
|
|
92
|
+
};
|
|
93
|
+
pickData.push(obj);
|
|
94
|
+
}
|
|
95
|
+
return pickData;
|
|
96
|
+
}
|
|
97
|
+
function toSingleStr(str) {
|
|
98
|
+
return str < 10 ? '0' + str : str;
|
|
99
|
+
}
|
|
100
|
+
function toStr(time) {
|
|
101
|
+
const [hour, minute] = formatStrToInt(time);
|
|
102
|
+
const newHour = toSingleStr(hour);
|
|
103
|
+
const newMinute = toSingleStr(minute);
|
|
104
|
+
return '' + newHour + newMinute;
|
|
105
|
+
}
|
|
106
|
+
function checkSelectedIsValid(strStart, strEnd, selected) {
|
|
107
|
+
const strSel = '' + toSingleStr(selected[0]) + toSingleStr(selected[1]);
|
|
108
|
+
if (strSel < strStart || strSel > strEnd)
|
|
109
|
+
return false;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* [{label:'', value: '', key: '', children: []}]
|
|
114
|
+
label: string | ReactNode
|
|
115
|
+
value: string | number
|
|
116
|
+
key?: string | number
|
|
117
|
+
children?: PickerColumnItem[]
|
|
118
|
+
*/
|
|
119
|
+
// start="02:10" end = 23:01
|
|
120
|
+
const _TimePicker = forwardRef((props, ref) => {
|
|
121
|
+
const { children, start, end, value, bindchange, bindcancel, style } = props;
|
|
122
|
+
const defaultProps = {
|
|
123
|
+
start: '00:10',
|
|
124
|
+
end: '23:59'
|
|
125
|
+
};
|
|
126
|
+
const defaultValue = formatStrToInt(value);
|
|
127
|
+
const [timevalue, setTimeValue] = useState(defaultValue);
|
|
128
|
+
// 存储layout布局信息
|
|
129
|
+
const layoutRef = useRef({});
|
|
130
|
+
const viewRef = useRef(null);
|
|
131
|
+
const nodeRef = useRef(null);
|
|
132
|
+
useNodesRef(props, ref, nodeRef, { style });
|
|
133
|
+
// 存储modal的布局信息
|
|
134
|
+
const modalLayoutRef = useRef({});
|
|
135
|
+
const modalRef = useRef(null);
|
|
136
|
+
const [visible, setVisible] = useState(false);
|
|
137
|
+
const columnData = generateColumns();
|
|
138
|
+
const [data, setData] = useState(columnData);
|
|
139
|
+
const [offsetTop, setOffsetTop] = useState(0);
|
|
140
|
+
const strStart = toStr(start);
|
|
141
|
+
const strEnd = toStr(end);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const newColumnData = generateColumns();
|
|
144
|
+
setData(newColumnData);
|
|
145
|
+
}, [start, end]);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (value) {
|
|
148
|
+
const nValue = formatStrToInt(value);
|
|
149
|
+
nValue && setTimeValue(nValue);
|
|
150
|
+
}
|
|
151
|
+
}, [value]);
|
|
152
|
+
// console.log('---------------visible---', visible, JSON.stringify(columnData))
|
|
153
|
+
const handleModalStatus = (status) => {
|
|
154
|
+
setVisible(status);
|
|
155
|
+
};
|
|
156
|
+
const handleConfirm = () => {
|
|
157
|
+
handleModalStatus(false);
|
|
158
|
+
bindchange && bindchange({
|
|
159
|
+
detail: {
|
|
160
|
+
value: formatStr(timevalue)
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
const handleCancel = () => {
|
|
165
|
+
handleModalStatus(false);
|
|
166
|
+
bindcancel && bindcancel();
|
|
167
|
+
};
|
|
168
|
+
const handleChildClick = () => {
|
|
169
|
+
handleModalStatus(true);
|
|
170
|
+
};
|
|
171
|
+
const handlePickChange = (date) => {
|
|
172
|
+
// 不是有效的值
|
|
173
|
+
if (!checkSelectedIsValid(strStart, strEnd, date)) {
|
|
174
|
+
setTimeValue(timevalue);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// [9, 13]
|
|
178
|
+
setTimeValue(date);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const onElementLayout = () => {
|
|
182
|
+
viewRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => {
|
|
183
|
+
layoutRef.current = { x, y, width, height, offsetLeft, offsetTop };
|
|
184
|
+
props.getInnerLayout && props.getInnerLayout(layoutRef);
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
const onModalLayout = () => {
|
|
188
|
+
modalRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => {
|
|
189
|
+
modalLayoutRef.current = { x, y, width, height, offsetLeft, offsetTop };
|
|
190
|
+
setOffsetTop(offsetTop);
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
const renderModalChildren = () => {
|
|
194
|
+
const pickerProps = {
|
|
195
|
+
ref: nodeRef,
|
|
196
|
+
data,
|
|
197
|
+
value: timevalue,
|
|
198
|
+
defaultValue: timevalue,
|
|
199
|
+
cols: 2,
|
|
200
|
+
onChange: handlePickChange
|
|
201
|
+
};
|
|
202
|
+
return (<View style={styles.centeredView} ref={modalRef} onLayout={onModalLayout}>
|
|
203
|
+
<View style={styles.btnLine}>
|
|
204
|
+
<View style={styles.cancel}>
|
|
205
|
+
<TouchableWithoutFeedback onPress={handleCancel}>
|
|
206
|
+
<Text style={styles.btntext}>取消</Text>
|
|
207
|
+
</TouchableWithoutFeedback>
|
|
208
|
+
</View>
|
|
209
|
+
<View style={styles.ok}>
|
|
210
|
+
<TouchableWithoutFeedback onPress={handleConfirm}>
|
|
211
|
+
<Text style={styles.btntext}>确定</Text>
|
|
212
|
+
</TouchableWithoutFeedback>
|
|
213
|
+
</View>
|
|
214
|
+
</View>
|
|
215
|
+
<PickerView {...pickerProps}></PickerView>
|
|
216
|
+
</View>);
|
|
217
|
+
};
|
|
218
|
+
const renderChildren = () => {
|
|
219
|
+
const touchProps = {
|
|
220
|
+
onLayout: onElementLayout,
|
|
221
|
+
ref: viewRef
|
|
222
|
+
};
|
|
223
|
+
return <View>
|
|
224
|
+
<TouchableWithoutFeedback onPress={handleChildClick}>
|
|
225
|
+
<View {...touchProps}>{children}</View>
|
|
226
|
+
</TouchableWithoutFeedback>
|
|
227
|
+
</View>;
|
|
228
|
+
};
|
|
229
|
+
// Animated.View
|
|
230
|
+
return (<>
|
|
231
|
+
<Portal>
|
|
232
|
+
<View style={visible ? styles.showModal : styles.hideModal}>
|
|
233
|
+
<Modal animationType="slide" transparent={true} visible={visible}>
|
|
234
|
+
{renderModalChildren()}
|
|
235
|
+
</Modal>
|
|
236
|
+
</View>
|
|
237
|
+
</Portal>
|
|
238
|
+
{renderChildren()}
|
|
239
|
+
</>);
|
|
240
|
+
});
|
|
241
|
+
_TimePicker.displayName = 'mpx-picker-time';
|
|
242
|
+
export default _TimePicker;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import Reanimated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
|
|
3
|
+
import { extendObject } from './utils';
|
|
4
|
+
import { createFaces } from './pickerFaces';
|
|
5
|
+
import { usePickerViewColumnAnimationContext, usePickerViewStyleContext } from './pickerVIewContext';
|
|
6
|
+
const PickerViewColumnItem = ({ item, index, itemHeight, itemWidth = '100%', textStyle, textProps, visibleCount, onItemLayout }) => {
|
|
7
|
+
const textStyleFromAncestor = usePickerViewStyleContext();
|
|
8
|
+
const offsetYShared = usePickerViewColumnAnimationContext();
|
|
9
|
+
const facesShared = useSharedValue(createFaces(itemHeight, visibleCount));
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
facesShared.value = createFaces(itemHeight, visibleCount);
|
|
12
|
+
}, [itemHeight]);
|
|
13
|
+
const animatedStyles = useAnimatedStyle(() => {
|
|
14
|
+
const inputRange = facesShared.value.map((f) => itemHeight * (index + f.index));
|
|
15
|
+
return {
|
|
16
|
+
opacity: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.opacity), Extrapolation.CLAMP),
|
|
17
|
+
transform: [
|
|
18
|
+
{ translateY: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.offsetY), Extrapolation.EXTEND) },
|
|
19
|
+
{ rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.CLAMP) + 'deg' },
|
|
20
|
+
{ scale: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.scale), Extrapolation.EXTEND) }
|
|
21
|
+
]
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
const strKey = `picker-column-item-${index}`;
|
|
25
|
+
const restProps = index === 0 ? { onLayout: onItemLayout } : {};
|
|
26
|
+
const itemProps = extendObject({
|
|
27
|
+
style: extendObject({ height: itemHeight, width: '100%' }, textStyleFromAncestor, textStyle, item.props.style)
|
|
28
|
+
}, textProps, restProps);
|
|
29
|
+
const realItem = React.cloneElement(item, itemProps);
|
|
30
|
+
return (<Reanimated.View key={strKey} style={[{ height: itemHeight, width: itemWidth }, animatedStyles]}>
|
|
31
|
+
{realItem}
|
|
32
|
+
</Reanimated.View>);
|
|
33
|
+
};
|
|
34
|
+
PickerViewColumnItem.displayName = 'MpxPickerViewColumnItem';
|
|
35
|
+
export default PickerViewColumnItem;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import Reanimated, { useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated';
|
|
4
|
+
import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, isAndroid, isIOS } from './utils';
|
|
5
|
+
import useNodesRef from './useNodesRef';
|
|
6
|
+
import PickerIndicator from './pickerViewIndicator';
|
|
7
|
+
import PickerMask from './pickerViewMask';
|
|
8
|
+
import MpxPickerVIewColumnItem from './mpx-picker-view-column-item';
|
|
9
|
+
import { PickerViewColumnAnimationContext } from './pickerVIewContext';
|
|
10
|
+
const visibleCount = 5;
|
|
11
|
+
const _PickerViewColumn = forwardRef((props, ref) => {
|
|
12
|
+
const { columnData, columnIndex, initialIndex, onSelectChange, style, wrapperStyle, pickerMaskStyle, pickerIndicatorStyle, 'enable-var': enableVar, 'external-var-context': externalVarContext } = props;
|
|
13
|
+
const { normalStyle, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext });
|
|
14
|
+
const { textStyle = {} } = splitStyle(normalStyle);
|
|
15
|
+
const { textProps = {} } = splitProps(props);
|
|
16
|
+
const scrollViewRef = useAnimatedRef();
|
|
17
|
+
const offsetYShared = useScrollViewOffset(scrollViewRef);
|
|
18
|
+
useNodesRef(props, ref, scrollViewRef, {
|
|
19
|
+
style: normalStyle
|
|
20
|
+
});
|
|
21
|
+
const { height: pickerH, itemHeight } = wrapperStyle;
|
|
22
|
+
const [itemRawH, setItemRawH] = useState(itemHeight);
|
|
23
|
+
const maxIndex = useMemo(() => columnData.length - 1, [columnData]);
|
|
24
|
+
const prevScrollingInfo = useRef({ index: initialIndex, y: 0 });
|
|
25
|
+
const touching = useRef(false);
|
|
26
|
+
const scrolling = useRef(false);
|
|
27
|
+
const timerResetPosition = useRef(null);
|
|
28
|
+
const timerScrollTo = useRef(null);
|
|
29
|
+
const activeIndex = useRef(initialIndex);
|
|
30
|
+
const prevIndex = usePrevious(initialIndex);
|
|
31
|
+
const prevMaxIndex = usePrevious(maxIndex);
|
|
32
|
+
const { layoutProps } = useLayout({
|
|
33
|
+
props,
|
|
34
|
+
hasSelfPercent,
|
|
35
|
+
setWidth,
|
|
36
|
+
setHeight,
|
|
37
|
+
nodeRef: scrollViewRef
|
|
38
|
+
});
|
|
39
|
+
const paddingHeight = useMemo(() => Math.round((pickerH - itemHeight) / 2), [pickerH, itemHeight]);
|
|
40
|
+
const snapToOffsets = useMemo(() => Array.from({ length: maxIndex + 1 }, (_, i) => i * itemRawH), [maxIndex, itemRawH]);
|
|
41
|
+
const contentContainerStyle = useMemo(() => {
|
|
42
|
+
return [{ paddingVertical: paddingHeight }];
|
|
43
|
+
}, [paddingHeight]);
|
|
44
|
+
const getIndex = useCallback((y) => {
|
|
45
|
+
const calc = Math.round(y / itemRawH);
|
|
46
|
+
return Math.max(0, Math.min(calc, maxIndex));
|
|
47
|
+
}, [itemRawH, maxIndex]);
|
|
48
|
+
const clearTimerResetPosition = useCallback(() => {
|
|
49
|
+
if (timerResetPosition.current) {
|
|
50
|
+
clearTimeout(timerResetPosition.current);
|
|
51
|
+
timerResetPosition.current = null;
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
const clearTimerScrollTo = useCallback(() => {
|
|
55
|
+
if (timerScrollTo.current) {
|
|
56
|
+
clearTimeout(timerScrollTo.current);
|
|
57
|
+
timerScrollTo.current = null;
|
|
58
|
+
}
|
|
59
|
+
}, []);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
return () => {
|
|
62
|
+
clearTimerResetPosition();
|
|
63
|
+
clearTimerScrollTo();
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!scrollViewRef.current ||
|
|
68
|
+
!itemRawH ||
|
|
69
|
+
touching.current ||
|
|
70
|
+
scrolling.current ||
|
|
71
|
+
prevIndex == null ||
|
|
72
|
+
initialIndex === prevIndex ||
|
|
73
|
+
initialIndex === activeIndex.current ||
|
|
74
|
+
maxIndex !== prevMaxIndex) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
clearTimerScrollTo();
|
|
78
|
+
timerScrollTo.current = setTimeout(() => {
|
|
79
|
+
scrollViewRef.current?.scrollTo({
|
|
80
|
+
x: 0,
|
|
81
|
+
y: initialIndex * itemRawH,
|
|
82
|
+
animated: false
|
|
83
|
+
});
|
|
84
|
+
}, isAndroid ? 200 : 0);
|
|
85
|
+
activeIndex.current = initialIndex;
|
|
86
|
+
}, [itemRawH, maxIndex, initialIndex]);
|
|
87
|
+
const onContentSizeChange = useCallback((_w, h) => {
|
|
88
|
+
const y = initialIndex * itemRawH;
|
|
89
|
+
if (y <= h) {
|
|
90
|
+
clearTimerScrollTo();
|
|
91
|
+
timerScrollTo.current = setTimeout(() => {
|
|
92
|
+
scrollViewRef.current?.scrollTo({ x: 0, y, animated: false });
|
|
93
|
+
}, 0);
|
|
94
|
+
}
|
|
95
|
+
}, [itemRawH, initialIndex]);
|
|
96
|
+
const onItemLayout = useCallback((e) => {
|
|
97
|
+
const { height: rawH } = e.nativeEvent.layout;
|
|
98
|
+
const roundedH = Math.round(rawH);
|
|
99
|
+
if (roundedH && roundedH !== itemRawH) {
|
|
100
|
+
setItemRawH(roundedH);
|
|
101
|
+
}
|
|
102
|
+
}, [itemRawH]);
|
|
103
|
+
const resetScrollPosition = useCallback((y) => {
|
|
104
|
+
if (touching.current || scrolling.current) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
scrolling.current = true;
|
|
108
|
+
const targetIndex = getIndex(y);
|
|
109
|
+
scrollViewRef.current?.scrollTo({ x: 0, y: targetIndex * itemRawH, animated: false });
|
|
110
|
+
}, [itemRawH, getIndex]);
|
|
111
|
+
const onMomentumScrollBegin = useCallback(() => {
|
|
112
|
+
isIOS && clearTimerResetPosition();
|
|
113
|
+
scrolling.current = true;
|
|
114
|
+
}, []);
|
|
115
|
+
const onMomentumScrollEnd = useCallback((e) => {
|
|
116
|
+
scrolling.current = false;
|
|
117
|
+
const { y: scrollY } = e.nativeEvent.contentOffset;
|
|
118
|
+
if (isIOS && scrollY % itemRawH !== 0) {
|
|
119
|
+
return resetScrollPosition(scrollY);
|
|
120
|
+
}
|
|
121
|
+
const calcIndex = getIndex(scrollY);
|
|
122
|
+
if (calcIndex !== activeIndex.current) {
|
|
123
|
+
activeIndex.current = calcIndex;
|
|
124
|
+
onSelectChange(calcIndex);
|
|
125
|
+
}
|
|
126
|
+
}, [itemRawH, getIndex, onSelectChange, resetScrollPosition]);
|
|
127
|
+
const onScrollBeginDrag = useCallback(() => {
|
|
128
|
+
isIOS && clearTimerResetPosition();
|
|
129
|
+
touching.current = true;
|
|
130
|
+
prevScrollingInfo.current = {
|
|
131
|
+
index: activeIndex.current,
|
|
132
|
+
y: activeIndex.current * itemRawH
|
|
133
|
+
};
|
|
134
|
+
}, [itemRawH]);
|
|
135
|
+
const onScrollEndDrag = useCallback((e) => {
|
|
136
|
+
touching.current = false;
|
|
137
|
+
if (isIOS) {
|
|
138
|
+
const { y } = e.nativeEvent.contentOffset;
|
|
139
|
+
if (y % itemRawH === 0) {
|
|
140
|
+
onMomentumScrollEnd({ nativeEvent: { contentOffset: { y } } });
|
|
141
|
+
}
|
|
142
|
+
else if (y > 0 && y < snapToOffsets[maxIndex]) {
|
|
143
|
+
timerResetPosition.current = setTimeout(() => {
|
|
144
|
+
resetScrollPosition(y);
|
|
145
|
+
}, 10);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}, [itemRawH, maxIndex, snapToOffsets, onMomentumScrollEnd, resetScrollPosition]);
|
|
149
|
+
const onScroll = useCallback((e) => {
|
|
150
|
+
// 全局注册的振动触感 hook
|
|
151
|
+
const pickerVibrate = global.__mpx?.config?.rnConfig?.pickerVibrate;
|
|
152
|
+
if (typeof pickerVibrate !== 'function') {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const { y } = e.nativeEvent.contentOffset;
|
|
156
|
+
const { index: prevIndex, y: _y } = prevScrollingInfo.current;
|
|
157
|
+
if (touching.current || scrolling.current) {
|
|
158
|
+
if (Math.abs(y - _y) >= itemRawH) {
|
|
159
|
+
const currentId = getIndex(y);
|
|
160
|
+
if (currentId !== prevIndex) {
|
|
161
|
+
prevScrollingInfo.current = {
|
|
162
|
+
index: currentId,
|
|
163
|
+
y: currentId * itemRawH
|
|
164
|
+
};
|
|
165
|
+
// vibrateShort({ type: 'selection' })
|
|
166
|
+
pickerVibrate();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}, [itemRawH, getIndex]);
|
|
171
|
+
const renderInnerchild = () => columnData.map((item, index) => {
|
|
172
|
+
return (<MpxPickerVIewColumnItem key={index} item={item} index={index} itemHeight={itemHeight} textStyle={textStyle} textProps={textProps} visibleCount={visibleCount} onItemLayout={onItemLayout}/>);
|
|
173
|
+
});
|
|
174
|
+
const renderScollView = () => {
|
|
175
|
+
return (<PickerViewColumnAnimationContext.Provider value={offsetYShared}>
|
|
176
|
+
<Reanimated.ScrollView ref={scrollViewRef} bounces={true} horizontal={false} nestedScrollEnabled={true} removeClippedSubviews={false} showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} scrollEventThrottle={16} {...layoutProps} style={[{ width: '100%' }]} decelerationRate="fast" snapToOffsets={snapToOffsets} onScroll={onScroll} onScrollBeginDrag={onScrollBeginDrag} onScrollEndDrag={onScrollEndDrag} onMomentumScrollBegin={onMomentumScrollBegin} onMomentumScrollEnd={onMomentumScrollEnd} onContentSizeChange={onContentSizeChange} contentContainerStyle={contentContainerStyle}>
|
|
177
|
+
{renderInnerchild()}
|
|
178
|
+
</Reanimated.ScrollView>
|
|
179
|
+
</PickerViewColumnAnimationContext.Provider>);
|
|
180
|
+
};
|
|
181
|
+
const renderIndicator = () => (<PickerIndicator itemHeight={itemHeight} indicatorItemStyle={pickerIndicatorStyle}/>);
|
|
182
|
+
const renderMask = () => (<PickerMask itemHeight={itemHeight} maskContainerStyle={pickerMaskStyle}/>);
|
|
183
|
+
return (<View style={[styles.wrapper, normalStyle]}>
|
|
184
|
+
{renderScollView()}
|
|
185
|
+
{renderMask()}
|
|
186
|
+
{renderIndicator()}
|
|
187
|
+
</View>);
|
|
188
|
+
});
|
|
189
|
+
const styles = StyleSheet.create({
|
|
190
|
+
wrapper: { display: 'flex', flex: 1 }
|
|
191
|
+
});
|
|
192
|
+
_PickerViewColumn.displayName = 'MpxPickerViewColumn';
|
|
193
|
+
export default _PickerViewColumn;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import React, { forwardRef, useRef } from 'react';
|
|
3
|
+
import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
4
|
+
import useNodesRef from './useNodesRef';
|
|
5
|
+
import { useLayout, splitProps, splitStyle, wrapChildren, useTransformStyle, extendObject } from './utils';
|
|
6
|
+
import { PickerViewStyleContext } from './pickerVIewContext';
|
|
7
|
+
const styles = {
|
|
8
|
+
wrapper: {
|
|
9
|
+
display: 'flex',
|
|
10
|
+
flex: 1,
|
|
11
|
+
flexDirection: 'row',
|
|
12
|
+
justifyContent: 'space-around',
|
|
13
|
+
overflow: 'hidden',
|
|
14
|
+
alignItems: 'center'
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const DefaultPickerItemH = 36;
|
|
18
|
+
const _PickerView = forwardRef((props, ref) => {
|
|
19
|
+
const { children, value = [], bindchange, style, 'indicator-style': indicatorStyle = {}, 'mask-style': pickerMaskStyle = {}, 'enable-var': enableVar, 'external-var-context': externalVarContext } = props;
|
|
20
|
+
const { height: indicatorH, ...pickerIndicatorStyle } = indicatorStyle;
|
|
21
|
+
const nodeRef = useRef(null);
|
|
22
|
+
const cloneRef = useRef(null);
|
|
23
|
+
const activeValueRef = useRef(value);
|
|
24
|
+
activeValueRef.current = value.slice();
|
|
25
|
+
const snapActiveValueRef = useRef(null);
|
|
26
|
+
const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext });
|
|
27
|
+
useNodesRef(props, ref, nodeRef, {
|
|
28
|
+
style: normalStyle
|
|
29
|
+
});
|
|
30
|
+
const { layoutRef, layoutProps, layoutStyle } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: nodeRef });
|
|
31
|
+
const { textProps } = splitProps(props);
|
|
32
|
+
const { textStyle } = splitStyle(normalStyle);
|
|
33
|
+
const onSelectChange = (columnIndex, selectedIndex) => {
|
|
34
|
+
const activeValue = activeValueRef.current;
|
|
35
|
+
activeValue[columnIndex] = selectedIndex;
|
|
36
|
+
const eventData = getCustomEvent('change', {}, { detail: { value: activeValue.slice(), source: 'change' }, layoutRef });
|
|
37
|
+
bindchange?.(eventData);
|
|
38
|
+
snapActiveValueRef.current = activeValueRef.current;
|
|
39
|
+
};
|
|
40
|
+
const hasDiff = (a = [], b) => {
|
|
41
|
+
return a.some((v, i) => v !== b[i]);
|
|
42
|
+
};
|
|
43
|
+
const onInitialChange = (isInvalid, value) => {
|
|
44
|
+
if (isInvalid || !snapActiveValueRef.current || hasDiff(snapActiveValueRef.current, value)) {
|
|
45
|
+
const eventData = getCustomEvent('change', {}, { detail: { value: value.slice(), source: 'change' }, layoutRef });
|
|
46
|
+
bindchange?.(eventData);
|
|
47
|
+
snapActiveValueRef.current = value.slice();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const innerProps = useInnerProps(props, extendObject({
|
|
51
|
+
ref: nodeRef,
|
|
52
|
+
style: extendObject({}, normalStyle, layoutStyle, {
|
|
53
|
+
position: 'relative',
|
|
54
|
+
overflow: 'hidden'
|
|
55
|
+
}),
|
|
56
|
+
layoutProps
|
|
57
|
+
}), [
|
|
58
|
+
'enable-offset',
|
|
59
|
+
'indicator-style',
|
|
60
|
+
'indicator-class',
|
|
61
|
+
'mask-style',
|
|
62
|
+
'mask-class'
|
|
63
|
+
], { layoutRef });
|
|
64
|
+
const renderColumn = (child, index, columnData, initialIndex) => {
|
|
65
|
+
const childProps = child?.props || {};
|
|
66
|
+
const wrappedProps = extendObject({}, childProps, {
|
|
67
|
+
columnData,
|
|
68
|
+
ref: cloneRef,
|
|
69
|
+
columnIndex: index,
|
|
70
|
+
key: `pick-view-${index}`,
|
|
71
|
+
wrapperStyle: {
|
|
72
|
+
height: normalStyle?.height || DefaultPickerItemH,
|
|
73
|
+
itemHeight: indicatorH || DefaultPickerItemH
|
|
74
|
+
},
|
|
75
|
+
onSelectChange: onSelectChange.bind(null, index),
|
|
76
|
+
initialIndex,
|
|
77
|
+
pickerIndicatorStyle,
|
|
78
|
+
pickerMaskStyle
|
|
79
|
+
});
|
|
80
|
+
const realElement = React.cloneElement(child, wrappedProps);
|
|
81
|
+
return wrapChildren({
|
|
82
|
+
children: realElement
|
|
83
|
+
}, {
|
|
84
|
+
hasVarDec,
|
|
85
|
+
varContext: varContextRef.current,
|
|
86
|
+
textStyle,
|
|
87
|
+
textProps
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const validateChildInitialIndex = (index, data) => {
|
|
91
|
+
return Math.max(0, Math.min(value[index] || 0, data.length - 1));
|
|
92
|
+
};
|
|
93
|
+
const flatColumnChildren = (data) => {
|
|
94
|
+
const columnData = React.Children.toArray(data?.props?.children);
|
|
95
|
+
if (columnData.length === 1 && React.isValidElement(columnData[0]) && columnData[0].type === React.Fragment) {
|
|
96
|
+
// 只有一个 Fragment 嵌套情况
|
|
97
|
+
return React.Children.toArray(columnData[0].props.children);
|
|
98
|
+
}
|
|
99
|
+
return columnData;
|
|
100
|
+
};
|
|
101
|
+
const renderPickerColumns = () => {
|
|
102
|
+
const columns = React.Children.toArray(children);
|
|
103
|
+
const renderColumns = [];
|
|
104
|
+
const validValue = [];
|
|
105
|
+
let isInvalid = false;
|
|
106
|
+
columns.forEach((item, index) => {
|
|
107
|
+
const columnData = flatColumnChildren(item);
|
|
108
|
+
const validIndex = validateChildInitialIndex(index, columnData);
|
|
109
|
+
if (validIndex !== value[index]) {
|
|
110
|
+
isInvalid = true;
|
|
111
|
+
}
|
|
112
|
+
validValue.push(validIndex);
|
|
113
|
+
renderColumns.push(renderColumn(item, index, columnData, validIndex));
|
|
114
|
+
});
|
|
115
|
+
onInitialChange(isInvalid, validValue);
|
|
116
|
+
return renderColumns;
|
|
117
|
+
};
|
|
118
|
+
return (<PickerViewStyleContext.Provider value={textStyle}>
|
|
119
|
+
<View {...innerProps}>
|
|
120
|
+
<View style={[styles.wrapper]}>{renderPickerColumns()}</View>
|
|
121
|
+
</View>
|
|
122
|
+
</PickerViewStyleContext.Provider>);
|
|
123
|
+
});
|
|
124
|
+
_PickerView.displayName = 'MpxPickerView';
|
|
125
|
+
export default _PickerView;
|