@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,289 @@
|
|
|
1
|
+
import { forwardRef, useRef, useContext, useMemo, useState, useEffect } from 'react';
|
|
2
|
+
import { warn, isFunction } from '@mpxjs/utils';
|
|
3
|
+
import Portal from './mpx-portal/index';
|
|
4
|
+
import { getCustomEvent } from './getInnerListeners';
|
|
5
|
+
import { promisify, redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy';
|
|
6
|
+
import { WebView } from 'react-native-webview';
|
|
7
|
+
import useNodesRef from './useNodesRef';
|
|
8
|
+
import { getCurrentPage, useNavigation } from './utils';
|
|
9
|
+
import { RouteContext } from './context';
|
|
10
|
+
import { StyleSheet, View, Text } from 'react-native';
|
|
11
|
+
const styles = StyleSheet.create({
|
|
12
|
+
loadErrorContext: {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
alignItems: 'center'
|
|
15
|
+
},
|
|
16
|
+
loadErrorText: {
|
|
17
|
+
fontSize: 12,
|
|
18
|
+
color: '#666666',
|
|
19
|
+
paddingTop: '40%',
|
|
20
|
+
paddingBottom: 20,
|
|
21
|
+
paddingLeft: '10%',
|
|
22
|
+
paddingRight: '10%',
|
|
23
|
+
textAlign: 'center'
|
|
24
|
+
},
|
|
25
|
+
loadErrorButton: {
|
|
26
|
+
color: '#666666',
|
|
27
|
+
textAlign: 'center',
|
|
28
|
+
padding: 10,
|
|
29
|
+
borderColor: '#666666',
|
|
30
|
+
borderStyle: 'solid',
|
|
31
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
32
|
+
borderRadius: 10
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const _WebView = forwardRef((props, ref) => {
|
|
36
|
+
const { src, bindmessage, bindload, binderror } = props;
|
|
37
|
+
const mpx = global.__mpx;
|
|
38
|
+
const errorText = {
|
|
39
|
+
'zh-CN': {
|
|
40
|
+
text: '网络不可用,请检查网络设置',
|
|
41
|
+
button: '重新加载'
|
|
42
|
+
},
|
|
43
|
+
'en-US': {
|
|
44
|
+
text: 'The network is not available. Please check the network settings',
|
|
45
|
+
button: 'Reload'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const currentErrorText = errorText[mpx.i18n.locale || 'zh-CN'];
|
|
49
|
+
if (props.style) {
|
|
50
|
+
warn('The web-view component does not support the style prop.');
|
|
51
|
+
}
|
|
52
|
+
const { pageId } = useContext(RouteContext) || {};
|
|
53
|
+
const [pageLoadErr, setPageLoadErr] = useState(false);
|
|
54
|
+
const currentPage = useMemo(() => getCurrentPage(pageId), [pageId]);
|
|
55
|
+
const webViewRef = useRef(null);
|
|
56
|
+
const fristLoaded = useRef(false);
|
|
57
|
+
const isLoadError = useRef(false);
|
|
58
|
+
const statusCode = useRef('');
|
|
59
|
+
const [isLoaded, setIsLoaded] = useState(true);
|
|
60
|
+
const defaultWebViewStyle = {
|
|
61
|
+
position: 'absolute',
|
|
62
|
+
left: 0,
|
|
63
|
+
right: 0,
|
|
64
|
+
top: 0,
|
|
65
|
+
bottom: 0
|
|
66
|
+
};
|
|
67
|
+
const canGoBack = useRef(false);
|
|
68
|
+
const isNavigateBack = useRef(false);
|
|
69
|
+
const beforeRemoveHandle = (e) => {
|
|
70
|
+
if (canGoBack.current && !isNavigateBack.current) {
|
|
71
|
+
webViewRef.current?.goBack();
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
}
|
|
74
|
+
isNavigateBack.current = false;
|
|
75
|
+
};
|
|
76
|
+
const navigation = useNavigation();
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const beforeRemoveSubscription = navigation?.addListener?.('beforeRemove', beforeRemoveHandle);
|
|
79
|
+
return () => {
|
|
80
|
+
if (isFunction(beforeRemoveSubscription)) {
|
|
81
|
+
beforeRemoveSubscription();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}, []);
|
|
85
|
+
useNodesRef(props, ref, webViewRef, {
|
|
86
|
+
style: defaultWebViewStyle
|
|
87
|
+
});
|
|
88
|
+
if (!src) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const _reload = function () {
|
|
92
|
+
if (__mpx_mode__ === 'android') {
|
|
93
|
+
fristLoaded.current = false; // 安卓需要重新设置
|
|
94
|
+
}
|
|
95
|
+
setPageLoadErr(false);
|
|
96
|
+
};
|
|
97
|
+
const injectedJavaScript = `
|
|
98
|
+
if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
|
|
99
|
+
var _documentTitle = document.title;
|
|
100
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
101
|
+
type: 'setTitle',
|
|
102
|
+
payload: {
|
|
103
|
+
_documentTitle: _documentTitle
|
|
104
|
+
}
|
|
105
|
+
}))
|
|
106
|
+
Object.defineProperty(document, 'title', {
|
|
107
|
+
set (val) {
|
|
108
|
+
_documentTitle = val
|
|
109
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
110
|
+
type: 'setTitle',
|
|
111
|
+
payload: {
|
|
112
|
+
_documentTitle: _documentTitle
|
|
113
|
+
}
|
|
114
|
+
}))
|
|
115
|
+
},
|
|
116
|
+
get () {
|
|
117
|
+
return _documentTitle
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
true;
|
|
122
|
+
`;
|
|
123
|
+
const sendMessage = function (params) {
|
|
124
|
+
return `
|
|
125
|
+
window.mpxWebviewMessageCallback && window.mpxWebviewMessageCallback(${params})
|
|
126
|
+
true;
|
|
127
|
+
`;
|
|
128
|
+
};
|
|
129
|
+
const _changeUrl = function (navState) {
|
|
130
|
+
if (navState.navigationType) { // navigationType这个事件在页面开始加载时和页面加载完成时都会被触发所以判断这个避免其他无效触发执行该逻辑
|
|
131
|
+
canGoBack.current = navState.canGoBack;
|
|
132
|
+
currentPage.__webViewUrl = navState.url;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const _onLoadProgress = function (event) {
|
|
136
|
+
if (__mpx_mode__ === 'android') {
|
|
137
|
+
canGoBack.current = event.nativeEvent.canGoBack;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const _message = function (res) {
|
|
141
|
+
let data = {};
|
|
142
|
+
let asyncCallback;
|
|
143
|
+
const navObj = promisify({ redirectTo, navigateTo, navigateBack, reLaunch, switchTab });
|
|
144
|
+
try {
|
|
145
|
+
const nativeEventData = res.nativeEvent?.data;
|
|
146
|
+
if (typeof nativeEventData === 'string') {
|
|
147
|
+
data = JSON.parse(nativeEventData);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (e) { }
|
|
151
|
+
const args = data.args;
|
|
152
|
+
const postData = data.payload || {};
|
|
153
|
+
const params = Array.isArray(args) ? args : [postData];
|
|
154
|
+
const type = data.type;
|
|
155
|
+
switch (type) {
|
|
156
|
+
case 'setTitle':
|
|
157
|
+
{ // case下不允许直接声明,包个块解决该问题
|
|
158
|
+
const title = postData._documentTitle;
|
|
159
|
+
if (title) {
|
|
160
|
+
navigation && navigation.setOptions({ title });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
case 'postMessage':
|
|
165
|
+
bindmessage && bindmessage(getCustomEvent('messsage', {}, {
|
|
166
|
+
detail: {
|
|
167
|
+
data: params[0]?.data
|
|
168
|
+
}
|
|
169
|
+
}));
|
|
170
|
+
asyncCallback = Promise.resolve({
|
|
171
|
+
errMsg: 'invokeWebappApi:ok'
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
case 'navigateTo':
|
|
175
|
+
asyncCallback = navObj.navigateTo(...params);
|
|
176
|
+
break;
|
|
177
|
+
case 'navigateBack':
|
|
178
|
+
isNavigateBack.current = true;
|
|
179
|
+
asyncCallback = navObj.navigateBack(...params);
|
|
180
|
+
break;
|
|
181
|
+
case 'redirectTo':
|
|
182
|
+
asyncCallback = navObj.redirectTo(...params);
|
|
183
|
+
break;
|
|
184
|
+
case 'switchTab':
|
|
185
|
+
asyncCallback = navObj.switchTab(...params);
|
|
186
|
+
break;
|
|
187
|
+
case 'reLaunch':
|
|
188
|
+
asyncCallback = navObj.reLaunch(...params);
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
if (type) {
|
|
192
|
+
const implement = mpx.config.webviewConfig.apiImplementations && mpx.config.webviewConfig.apiImplementations[type];
|
|
193
|
+
if (isFunction(implement)) {
|
|
194
|
+
asyncCallback = Promise.resolve(implement(...params));
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
/* eslint-disable prefer-promise-reject-errors */
|
|
198
|
+
asyncCallback = Promise.reject({
|
|
199
|
+
errMsg: `未在apiImplementations中配置${type}方法`
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
asyncCallback && asyncCallback.then((res) => {
|
|
206
|
+
if (webViewRef.current?.postMessage) {
|
|
207
|
+
const result = JSON.stringify({
|
|
208
|
+
type,
|
|
209
|
+
callbackId: data.callbackId,
|
|
210
|
+
result: res
|
|
211
|
+
});
|
|
212
|
+
webViewRef.current.injectJavaScript(sendMessage(result));
|
|
213
|
+
}
|
|
214
|
+
}).catch((error) => {
|
|
215
|
+
if (webViewRef.current?.postMessage) {
|
|
216
|
+
const result = JSON.stringify({
|
|
217
|
+
type,
|
|
218
|
+
callbackId: data.callbackId,
|
|
219
|
+
error
|
|
220
|
+
});
|
|
221
|
+
webViewRef.current.injectJavaScript(sendMessage(result));
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
const onLoadEndHandle = function (res) {
|
|
226
|
+
fristLoaded.current = true;
|
|
227
|
+
setIsLoaded(true);
|
|
228
|
+
const src = res.nativeEvent?.url;
|
|
229
|
+
if (isLoadError.current) {
|
|
230
|
+
isLoadError.current = false;
|
|
231
|
+
isNavigateBack.current = false;
|
|
232
|
+
const result = {
|
|
233
|
+
type: 'error',
|
|
234
|
+
timeStamp: res.timeStamp,
|
|
235
|
+
detail: {
|
|
236
|
+
src,
|
|
237
|
+
statusCode: statusCode.current
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
binderror && binderror(result);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
const result = {
|
|
244
|
+
type: 'load',
|
|
245
|
+
timeStamp: res.timeStamp,
|
|
246
|
+
detail: {
|
|
247
|
+
src
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
bindload?.(result);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const onLoadEnd = function (res) {
|
|
254
|
+
if (__mpx_mode__ === 'android') {
|
|
255
|
+
setTimeout(() => {
|
|
256
|
+
onLoadEndHandle(res);
|
|
257
|
+
}, 0);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
onLoadEndHandle(res);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const onHttpError = function (res) {
|
|
264
|
+
isLoadError.current = true;
|
|
265
|
+
statusCode.current = res.nativeEvent?.statusCode;
|
|
266
|
+
};
|
|
267
|
+
const onError = function () {
|
|
268
|
+
statusCode.current = '';
|
|
269
|
+
isLoadError.current = true;
|
|
270
|
+
if (!fristLoaded.current) {
|
|
271
|
+
setPageLoadErr(true);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
const onLoadStart = function () {
|
|
275
|
+
if (!fristLoaded.current) {
|
|
276
|
+
setIsLoaded(false);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
return (<Portal key={pageLoadErr ? 'error' : 'webview'}>
|
|
280
|
+
{pageLoadErr
|
|
281
|
+
? (<View style={[styles.loadErrorContext, defaultWebViewStyle]}>
|
|
282
|
+
<View style={styles.loadErrorText}><Text style={{ fontSize: 14, color: '#999999' }}>{currentErrorText.text}</Text></View>
|
|
283
|
+
<View style={styles.loadErrorButton} onTouchEnd={_reload}><Text style={{ fontSize: 12, color: '#666666' }}>{currentErrorText.button}</Text></View>
|
|
284
|
+
</View>)
|
|
285
|
+
: (<WebView style={defaultWebViewStyle} source={{ uri: src }} ref={webViewRef} javaScriptEnabled={true} onNavigationStateChange={_changeUrl} onMessage={_message} injectedJavaScript={injectedJavaScript} onLoadProgress={_onLoadProgress} onLoadEnd={onLoadEnd} onHttpError={onHttpError} onError={onError} onLoadStart={onLoadStart} allowsBackForwardNavigationGestures={isLoaded}></WebView>)}
|
|
286
|
+
</Portal>);
|
|
287
|
+
});
|
|
288
|
+
_WebView.displayName = 'MpxWebview';
|
|
289
|
+
export default _WebView;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export class ExpressionParser {
|
|
2
|
+
tokens;
|
|
3
|
+
formatter;
|
|
4
|
+
functions;
|
|
5
|
+
current;
|
|
6
|
+
constructor(input, formatter = val => parseFloat(val), functions = {}) {
|
|
7
|
+
this.tokens = this.tokenize(input);
|
|
8
|
+
this.formatter = formatter;
|
|
9
|
+
this.functions = functions;
|
|
10
|
+
this.current = 0;
|
|
11
|
+
}
|
|
12
|
+
tokenize(input) {
|
|
13
|
+
const tokens = [];
|
|
14
|
+
const regex = /(\d+\.?\d*(?:px|rpx|%|vw|vh)?|[+\-*/(),]|\b[a-zA-Z_][a-zA-Z0-9_]*\b)/g;
|
|
15
|
+
let match;
|
|
16
|
+
while ((match = regex.exec(input))) {
|
|
17
|
+
if (/^\d+\.?\d*(?:px|rpx|%|vw|vh)?$/.test(match[0])) {
|
|
18
|
+
const lastToken = tokens[tokens.length - 1];
|
|
19
|
+
const last2Token = tokens[tokens.length - 2];
|
|
20
|
+
if (lastToken?.type === '-' && (!last2Token || /^[+\-*/(,]$/.test(last2Token?.type))) {
|
|
21
|
+
tokens.pop();
|
|
22
|
+
tokens.push({
|
|
23
|
+
type: 'NUMBER',
|
|
24
|
+
value: '-' + match[0]
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
tokens.push({
|
|
29
|
+
type: 'NUMBER',
|
|
30
|
+
value: match[0]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
tokens.push({
|
|
36
|
+
type: match[0],
|
|
37
|
+
value: match[0]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return tokens;
|
|
42
|
+
}
|
|
43
|
+
parse() {
|
|
44
|
+
return this.expression();
|
|
45
|
+
}
|
|
46
|
+
expression() {
|
|
47
|
+
let node = this.term();
|
|
48
|
+
while (this.current < this.tokens.length &&
|
|
49
|
+
(this.tokens[this.current].type === '+' || this.tokens[this.current].type === '-')) {
|
|
50
|
+
const operator = this.tokens[this.current].type;
|
|
51
|
+
this.current++;
|
|
52
|
+
const right = this.term();
|
|
53
|
+
node = this.applyOperator(operator, node, right);
|
|
54
|
+
}
|
|
55
|
+
return node;
|
|
56
|
+
}
|
|
57
|
+
term() {
|
|
58
|
+
let node = this.factor();
|
|
59
|
+
while (this.current < this.tokens.length &&
|
|
60
|
+
(this.tokens[this.current].type === '*' || this.tokens[this.current].type === '/')) {
|
|
61
|
+
const operator = this.tokens[this.current].type;
|
|
62
|
+
this.current++;
|
|
63
|
+
const right = this.factor();
|
|
64
|
+
node = this.applyOperator(operator, node, right);
|
|
65
|
+
}
|
|
66
|
+
return node;
|
|
67
|
+
}
|
|
68
|
+
factor() {
|
|
69
|
+
const token = this.tokens[this.current];
|
|
70
|
+
if (token.type === 'NUMBER') {
|
|
71
|
+
this.current++;
|
|
72
|
+
const numericValue = this.formatter(token.value);
|
|
73
|
+
return { type: 'NUMBER', value: numericValue };
|
|
74
|
+
}
|
|
75
|
+
else if (token.type === '(') {
|
|
76
|
+
this.current++;
|
|
77
|
+
const node = this.expression();
|
|
78
|
+
if (this.tokens[this.current].type !== ')') {
|
|
79
|
+
throw new Error('Expected closing parenthesis');
|
|
80
|
+
}
|
|
81
|
+
this.current++;
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
else if (this.functions[token.type]) {
|
|
85
|
+
this.current++;
|
|
86
|
+
if (this.tokens[this.current].type !== '(') {
|
|
87
|
+
throw new Error('Expected opening parenthesis after function');
|
|
88
|
+
}
|
|
89
|
+
this.current++;
|
|
90
|
+
const args = this.parseArguments();
|
|
91
|
+
if (this.tokens[this.current].type !== ')') {
|
|
92
|
+
throw new Error('Expected closing parenthesis');
|
|
93
|
+
}
|
|
94
|
+
this.current++;
|
|
95
|
+
return this.applyFunction(token.type, args);
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Unexpected token: ${token.type}`);
|
|
98
|
+
}
|
|
99
|
+
parseArguments() {
|
|
100
|
+
const args = [];
|
|
101
|
+
while (this.current < this.tokens.length && this.tokens[this.current].type !== ')') {
|
|
102
|
+
args.push(this.expression());
|
|
103
|
+
if (this.tokens[this.current].type === ',') {
|
|
104
|
+
this.current++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return args;
|
|
108
|
+
}
|
|
109
|
+
applyOperator(operator, left, right) {
|
|
110
|
+
const leftVal = left.value;
|
|
111
|
+
const rightVal = right.value;
|
|
112
|
+
let result;
|
|
113
|
+
switch (operator) {
|
|
114
|
+
case '+':
|
|
115
|
+
result = leftVal + rightVal;
|
|
116
|
+
break;
|
|
117
|
+
case '-':
|
|
118
|
+
result = leftVal - rightVal;
|
|
119
|
+
break;
|
|
120
|
+
case '*':
|
|
121
|
+
result = leftVal * rightVal;
|
|
122
|
+
break;
|
|
123
|
+
case '/':
|
|
124
|
+
result = leftVal / rightVal;
|
|
125
|
+
break;
|
|
126
|
+
default: throw new Error(`Unknown operator: ${operator}`);
|
|
127
|
+
}
|
|
128
|
+
return { type: 'NUMBER', value: result };
|
|
129
|
+
}
|
|
130
|
+
applyFunction(func, args) {
|
|
131
|
+
if (args.some(arg => arg.type !== 'NUMBER')) {
|
|
132
|
+
throw new Error('Function arguments must be numbers');
|
|
133
|
+
}
|
|
134
|
+
const numericArgs = args.map(arg => arg.value);
|
|
135
|
+
if (this.functions[func]) {
|
|
136
|
+
return { type: 'NUMBER', value: this.functions[func].apply(null, numericArgs) };
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
throw new Error(`Unknown function: ${func}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export function parseFunc(str, funcName) {
|
|
144
|
+
const regex = new RegExp(`${funcName}\\(`, 'g');
|
|
145
|
+
const result = [];
|
|
146
|
+
let match;
|
|
147
|
+
while ((match = regex.exec(str)) !== null) {
|
|
148
|
+
const start = match.index;
|
|
149
|
+
let i = start + funcName.length + 1;
|
|
150
|
+
let depth = 1;
|
|
151
|
+
const args = [];
|
|
152
|
+
let arg = '';
|
|
153
|
+
while (depth && i < str.length) {
|
|
154
|
+
if (depth === 1 && (str[i] === ',' || str[i] === ')')) {
|
|
155
|
+
args.push(arg.trim());
|
|
156
|
+
arg = '';
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
arg += str[i];
|
|
160
|
+
}
|
|
161
|
+
switch (str[i]) {
|
|
162
|
+
case '(':
|
|
163
|
+
depth++;
|
|
164
|
+
break;
|
|
165
|
+
case ')':
|
|
166
|
+
depth--;
|
|
167
|
+
break;
|
|
168
|
+
default:
|
|
169
|
+
// Do nothing
|
|
170
|
+
}
|
|
171
|
+
i++;
|
|
172
|
+
}
|
|
173
|
+
const end = regex.lastIndex = i;
|
|
174
|
+
result.push({
|
|
175
|
+
start,
|
|
176
|
+
end,
|
|
177
|
+
args
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
export class ReplaceSource {
|
|
183
|
+
_source;
|
|
184
|
+
_replacements;
|
|
185
|
+
constructor(source) {
|
|
186
|
+
this._source = source;
|
|
187
|
+
this._replacements = [];
|
|
188
|
+
}
|
|
189
|
+
replace(start, end, content) {
|
|
190
|
+
this._replacements.push({ start, end, content });
|
|
191
|
+
}
|
|
192
|
+
source() {
|
|
193
|
+
if (this._replacements.length === 0) {
|
|
194
|
+
return this._source;
|
|
195
|
+
}
|
|
196
|
+
let current = this._source;
|
|
197
|
+
let pos = 0;
|
|
198
|
+
const result = [];
|
|
199
|
+
for (const replacement of this._replacements) {
|
|
200
|
+
const start = Math.floor(replacement.start);
|
|
201
|
+
const end = Math.floor(replacement.end) + 1;
|
|
202
|
+
if (pos < start) {
|
|
203
|
+
const offset = start - pos;
|
|
204
|
+
result.push(current.slice(0, offset));
|
|
205
|
+
current = current.slice(offset);
|
|
206
|
+
pos = start;
|
|
207
|
+
}
|
|
208
|
+
result.push(replacement.content);
|
|
209
|
+
if (pos < end) {
|
|
210
|
+
const offset = end - pos;
|
|
211
|
+
current = current.slice(offset);
|
|
212
|
+
pos = end;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
result.push(current);
|
|
216
|
+
return result.join('');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Borrowed from open-source code: https://github.com/quidone/react-native-wheel-picker
|
|
3
|
+
* Special thanks to the authors for their contribution to the open-source community.
|
|
4
|
+
*/
|
|
5
|
+
export const degToRad = (deg) => (Math.PI * deg) / 180;
|
|
6
|
+
// Calculates the height of the element after rotating it relative to the user's screen.
|
|
7
|
+
const calcHeight = (degree, itemHeight) => itemHeight * Math.cos(degToRad(degree));
|
|
8
|
+
export const calcPickerHeight = (faces, itemHeight) => {
|
|
9
|
+
if (faces.length === 7) {
|
|
10
|
+
return itemHeight * 5;
|
|
11
|
+
}
|
|
12
|
+
return faces.reduce((r, v) => r + calcHeight(Math.abs(v.deg), itemHeight), 0);
|
|
13
|
+
};
|
|
14
|
+
export const createFaces = (itemHeight, visibleCount) => {
|
|
15
|
+
// e.g [30, 60, 90]
|
|
16
|
+
const getDegreesRelativeCenter = () => {
|
|
17
|
+
const maxStep = Math.trunc((visibleCount + 2) / 2); // + 2 because there are 2 more faces at 90 degrees
|
|
18
|
+
const stepDegree = 90 / maxStep;
|
|
19
|
+
const result = [];
|
|
20
|
+
for (let i = 1; i <= maxStep; i++) {
|
|
21
|
+
result.push(i * stepDegree);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
const getScreenHeightsAndOffsets = (degrees) => {
|
|
26
|
+
const screenHeights = degrees.map((deg) => calcHeight(deg, itemHeight));
|
|
27
|
+
const freeSpaces = screenHeights.map((screenHeight) => itemHeight - screenHeight);
|
|
28
|
+
const offsets = freeSpaces.map((freeSpace, index) => {
|
|
29
|
+
let offset = freeSpace / 2;
|
|
30
|
+
for (let i = 0; i < index; i++) {
|
|
31
|
+
offset += freeSpaces[i];
|
|
32
|
+
}
|
|
33
|
+
return offset;
|
|
34
|
+
});
|
|
35
|
+
return [screenHeights, offsets];
|
|
36
|
+
};
|
|
37
|
+
const getOpacity = (index) => {
|
|
38
|
+
const map = {
|
|
39
|
+
0: 0,
|
|
40
|
+
1: 0.8,
|
|
41
|
+
2: 0.9
|
|
42
|
+
};
|
|
43
|
+
return map[index] ?? Math.min(1, map[2] + index * 0.05);
|
|
44
|
+
};
|
|
45
|
+
const degrees = getDegreesRelativeCenter();
|
|
46
|
+
const [screenHeight, offsets] = getScreenHeightsAndOffsets(degrees);
|
|
47
|
+
const scales = [0.973, 0.9, 0.8];
|
|
48
|
+
return [
|
|
49
|
+
// top items
|
|
50
|
+
...degrees
|
|
51
|
+
.map((degree, index) => {
|
|
52
|
+
return {
|
|
53
|
+
index: -1 * (index + 1),
|
|
54
|
+
deg: degree,
|
|
55
|
+
opacity: getOpacity(degrees.length - 1 - index),
|
|
56
|
+
offsetY: -1 * offsets[index],
|
|
57
|
+
scale: scales[index],
|
|
58
|
+
screenHeight: screenHeight[index]
|
|
59
|
+
};
|
|
60
|
+
})
|
|
61
|
+
.reverse(),
|
|
62
|
+
// center item
|
|
63
|
+
{ index: 0, deg: 0, opacity: 1, offsetY: 0, scale: 1, screenHeight: itemHeight },
|
|
64
|
+
// bottom items
|
|
65
|
+
...degrees.map((degree, index) => {
|
|
66
|
+
return {
|
|
67
|
+
index: index + 1,
|
|
68
|
+
deg: -1 * degree,
|
|
69
|
+
opacity: getOpacity(degrees.length - 1 - index),
|
|
70
|
+
offsetY: offsets[index],
|
|
71
|
+
scale: scales[index],
|
|
72
|
+
screenHeight: screenHeight[index]
|
|
73
|
+
};
|
|
74
|
+
})
|
|
75
|
+
];
|
|
76
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
export const PickerViewColumnAnimationContext = createContext(undefined);
|
|
3
|
+
export const usePickerViewColumnAnimationContext = () => {
|
|
4
|
+
const value = useContext(PickerViewColumnAnimationContext);
|
|
5
|
+
if (value === undefined) {
|
|
6
|
+
throw new Error('usePickerViewColumnAnimationContext must be called from within PickerViewColumnAnimationContext.Provider!');
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
export const PickerViewStyleContext = createContext(undefined);
|
|
11
|
+
export const usePickerViewStyleContext = () => {
|
|
12
|
+
const value = useContext(PickerViewStyleContext);
|
|
13
|
+
return value;
|
|
14
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
const _PickerViewIndicator = ({ itemHeight, indicatorItemStyle, indicatorContainerStyle }) => {
|
|
4
|
+
return (<View style={[styles.indicatorContainer, indicatorContainerStyle]} pointerEvents={'none'}>
|
|
5
|
+
<View style={[styles.selection, { height: itemHeight }, indicatorItemStyle]}/>
|
|
6
|
+
</View>);
|
|
7
|
+
};
|
|
8
|
+
const styles = StyleSheet.create({
|
|
9
|
+
indicatorContainer: {
|
|
10
|
+
...StyleSheet.absoluteFillObject,
|
|
11
|
+
justifyContent: 'center',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
zIndex: 200
|
|
14
|
+
},
|
|
15
|
+
selection: {
|
|
16
|
+
borderTopWidth: 1,
|
|
17
|
+
borderBottomWidth: 1,
|
|
18
|
+
borderColor: 'rgba(0, 0, 0, 0.05)',
|
|
19
|
+
alignSelf: 'stretch'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
_PickerViewIndicator.displayName = 'MpxPickerViewIndicator';
|
|
23
|
+
export default _PickerViewIndicator;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import LinearGradient from 'react-native-linear-gradient';
|
|
4
|
+
const _PickerViewMask = ({ itemHeight, maskContainerStyle }) => {
|
|
5
|
+
return (<View style={[styles.maskContainer, maskContainerStyle]} pointerEvents={'none'}>
|
|
6
|
+
<LinearGradient colors={['rgba(255,255,255,1)', 'rgba(255,255,255,0.5)']} style={{ flex: 1 }}/>
|
|
7
|
+
<View style={{ height: itemHeight }}/>
|
|
8
|
+
<LinearGradient colors={['rgba(255,255,255,0.5)', 'rgba(255,255,255,1)']} style={{ flex: 1 }}/>
|
|
9
|
+
</View>);
|
|
10
|
+
};
|
|
11
|
+
const styles = StyleSheet.create({
|
|
12
|
+
maskContainer: {
|
|
13
|
+
...StyleSheet.absoluteFillObject,
|
|
14
|
+
zIndex: 100
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
_PickerViewMask.displayName = 'MpxPickerViewMask';
|
|
18
|
+
export default _PickerViewMask;
|