@mpxjs/webpack-plugin 2.9.69-beta.2 → 2.9.69-beta.4
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/react/processScript.js +1 -1
- package/lib/runtime/components/react/context.ts +17 -0
- package/lib/runtime/components/react/dist/context.js +2 -0
- package/lib/runtime/components/react/dist/getInnerListeners.js +2 -2
- package/lib/runtime/components/react/dist/locale-provider.jsx +15 -0
- package/lib/runtime/components/react/dist/mpx-button.jsx +9 -37
- package/lib/runtime/components/react/dist/mpx-image.jsx +13 -9
- package/lib/runtime/components/react/dist/mpx-picker/time.jsx +2 -1
- package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +12 -13
- package/lib/runtime/components/react/dist/mpx-portal/portal-consumer.jsx +23 -0
- package/lib/runtime/components/react/dist/mpx-portal/portal-host.jsx +124 -0
- package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +40 -0
- package/lib/runtime/components/react/dist/mpx-portal.jsx +12 -0
- package/lib/runtime/components/react/dist/mpx-provider.jsx +31 -0
- package/lib/runtime/components/react/dist/mpx-root-portal.jsx +1 -1
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +10 -16
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +10 -5
- package/lib/runtime/components/react/dist/mpx-view.jsx +15 -21
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +78 -20
- package/lib/runtime/components/react/dist/pickerFaces.js +1 -1
- package/lib/runtime/components/react/dist/useAnimationHooks.js +14 -2
- package/lib/runtime/components/react/getInnerListeners.ts +2 -2
- package/lib/runtime/components/react/locale-provider.tsx +83 -0
- package/lib/runtime/components/react/mpx-button.tsx +13 -49
- package/lib/runtime/components/react/mpx-image.tsx +41 -25
- package/lib/runtime/components/react/mpx-picker/time.tsx +2 -1
- package/lib/runtime/components/react/mpx-picker-view-column.tsx +12 -13
- package/lib/runtime/components/react/mpx-portal/portal-consumer.tsx +32 -0
- package/lib/runtime/components/react/mpx-portal/portal-host.tsx +158 -0
- package/lib/runtime/components/react/mpx-portal/portal-manager.tsx +64 -0
- package/lib/runtime/components/react/mpx-portal.tsx +29 -0
- package/lib/runtime/components/react/mpx-provider.tsx +51 -0
- package/lib/runtime/components/react/mpx-root-portal.tsx +1 -1
- package/lib/runtime/components/react/mpx-scroll-view.tsx +9 -15
- package/lib/runtime/components/react/mpx-swiper.tsx +11 -5
- package/lib/runtime/components/react/mpx-view.tsx +17 -23
- package/lib/runtime/components/react/mpx-web-view.tsx +110 -21
- package/lib/runtime/components/react/pickerFaces.ts +1 -1
- package/lib/runtime/components/react/types/global.d.ts +3 -0
- package/lib/runtime/components/react/useAnimationHooks.ts +19 -3
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@ module.exports = function (script, {
|
|
|
26
26
|
import { getComponent } from ${stringifyRequest(loaderContext, optionProcessorPath)}
|
|
27
27
|
import { NavigationContainer, StackActions } from '@react-navigation/native'
|
|
28
28
|
import { createStackNavigator } from '@react-navigation/stack'
|
|
29
|
-
import
|
|
29
|
+
import Provider from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-provider'
|
|
30
30
|
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
31
31
|
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
32
32
|
|
|
@@ -32,6 +32,19 @@ export interface IntersectionObserver {
|
|
|
32
32
|
throttleMeasure: () => void
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
export interface PortalManagerContextValue {
|
|
36
|
+
mount: (key: number, children: React.ReactNode) => void
|
|
37
|
+
update: (key: number, children: React.ReactNode) => void
|
|
38
|
+
unmount: (key: number) => void,
|
|
39
|
+
portals: Array<{key: number, children: React.ReactNode}>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PortalContextValue {
|
|
43
|
+
mount: (children: React.ReactNode, key?: number, pageId?: number|null) => number| undefined
|
|
44
|
+
update: (key: number, children: React.ReactNode, pageId?: number|null) => void
|
|
45
|
+
unmount: (key: number, pageId?: number|null) => void
|
|
46
|
+
manager?: PortalManagerContextValue
|
|
47
|
+
}
|
|
35
48
|
|
|
36
49
|
export interface ScrollViewContextValue {
|
|
37
50
|
gestureRef: React.RefObject<any> | null
|
|
@@ -60,3 +73,7 @@ export const SwiperContext = createContext({})
|
|
|
60
73
|
export const KeyboardAvoidContext = createContext<KeyboardAvoidContextValue | null>(null)
|
|
61
74
|
|
|
62
75
|
export const ScrollViewContext = createContext<ScrollViewContextValue>({ gestureRef: null })
|
|
76
|
+
|
|
77
|
+
export const PortalContext = createContext<PortalContextValue>(null as any)
|
|
78
|
+
|
|
79
|
+
export const PortalManagerContext = createContext<PortalManagerContextValue| null>(null)
|
|
@@ -11,3 +11,5 @@ export const RouteContext = createContext(null);
|
|
|
11
11
|
export const SwiperContext = createContext({});
|
|
12
12
|
export const KeyboardAvoidContext = createContext(null);
|
|
13
13
|
export const ScrollViewContext = createContext({ gestureRef: null });
|
|
14
|
+
export const PortalContext = createContext(null);
|
|
15
|
+
export const PortalManagerContext = createContext(null);
|
|
@@ -82,8 +82,8 @@ function checkIsNeedPress(e, type, ref) {
|
|
|
82
82
|
const nativeEvent = e.nativeEvent;
|
|
83
83
|
const currentPageX = nativeEvent.changedTouches[0].pageX;
|
|
84
84
|
const currentPageY = nativeEvent.changedTouches[0].pageY;
|
|
85
|
-
if (Math.abs(currentPageX - tapDetailInfo.x) >
|
|
86
|
-
Math.abs(currentPageY - tapDetailInfo.y) >
|
|
85
|
+
if (Math.abs(currentPageX - tapDetailInfo.x) > 3 ||
|
|
86
|
+
Math.abs(currentPageY - tapDetailInfo.y) > 3) {
|
|
87
87
|
ref.current.needPress[type] = false;
|
|
88
88
|
ref.current.startTimer[type] &&
|
|
89
89
|
clearTimeout(ref.current.startTimer[type]);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useMemo, memo } from 'react';
|
|
2
|
+
import { extendObject } from './utils';
|
|
3
|
+
export const LocaleContext = createContext(undefined);
|
|
4
|
+
const LocaleProvider = (props) => {
|
|
5
|
+
const locale = useMemo(() => {
|
|
6
|
+
return {
|
|
7
|
+
antLocale: extendObject({}, props.locale, { exist: true })
|
|
8
|
+
};
|
|
9
|
+
}, [props.locale]);
|
|
10
|
+
return (<LocaleContext.Provider value={locale}>
|
|
11
|
+
{props.children}
|
|
12
|
+
</LocaleContext.Provider>);
|
|
13
|
+
};
|
|
14
|
+
LocaleProvider.displayName = 'LocaleProvider';
|
|
15
|
+
export default memo(LocaleProvider);
|
|
@@ -34,10 +34,11 @@
|
|
|
34
34
|
* ✘ bindagreeprivacyauthorization
|
|
35
35
|
* ✔ bindtap
|
|
36
36
|
*/
|
|
37
|
-
import { createElement, useEffect, useRef,
|
|
37
|
+
import { createElement, useEffect, useRef, forwardRef, useContext } from 'react';
|
|
38
38
|
import { View, StyleSheet, Animated, Easing } from 'react-native';
|
|
39
39
|
import { warn } from '@mpxjs/utils';
|
|
40
|
-
import {
|
|
40
|
+
import { GestureDetector } from 'react-native-gesture-handler';
|
|
41
|
+
import { getCurrentPage, splitProps, splitStyle, useLayout, useTransformStyle, wrapChildren, extendObject, useHoverStyle } from './utils';
|
|
41
42
|
import useInnerProps, { getCustomEvent } from './getInnerListeners';
|
|
42
43
|
import useNodesRef from './useNodesRef';
|
|
43
44
|
import { RouteContext, FormContext } from './context';
|
|
@@ -128,20 +129,16 @@ const Loading = ({ alone = false }) => {
|
|
|
128
129
|
};
|
|
129
130
|
const Button = forwardRef((buttonProps, ref) => {
|
|
130
131
|
const { textProps, innerProps: props = {} } = splitProps(buttonProps);
|
|
131
|
-
const { size = 'default', type = 'default', plain = false, disabled = false, loading = false, 'hover-class': hoverClass, 'hover-style': hoverStyle = {}, 'hover-start-time': hoverStartTime = 20, 'hover-stay-time': hoverStayTime = 70, 'open-type': openType, 'form-type': formType, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, style = {}, children, bindgetuserinfo, bindtap
|
|
132
|
+
const { size = 'default', type = 'default', plain = false, disabled = false, loading = false, 'hover-class': hoverClass, 'hover-style': hoverStyle = {}, 'hover-start-time': hoverStartTime = 20, 'hover-stay-time': hoverStayTime = 70, 'open-type': openType, 'form-type': formType, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, style = {}, children, bindgetuserinfo, bindtap } = props;
|
|
132
133
|
const pageId = useContext(RouteContext);
|
|
133
134
|
const formContext = useContext(FormContext);
|
|
135
|
+
const { isHover, enableHoverStyle, gesture } = useHoverStyle({ hoverStyle, hoverStartTime, hoverStayTime, disabled });
|
|
134
136
|
let submitFn;
|
|
135
137
|
let resetFn;
|
|
136
138
|
if (formContext) {
|
|
137
139
|
submitFn = formContext.submit;
|
|
138
140
|
resetFn = formContext.reset;
|
|
139
141
|
}
|
|
140
|
-
const refs = useRef({
|
|
141
|
-
hoverStartTimer: undefined,
|
|
142
|
-
hoverStayTimer: undefined
|
|
143
|
-
});
|
|
144
|
-
const [isHover, setIsHover] = useState(false);
|
|
145
142
|
const isMiniSize = size === 'mini';
|
|
146
143
|
const applyHoverEffect = isHover && hoverClass !== 'none';
|
|
147
144
|
const [color, hoverColor, plainColor, disabledColor] = TypeColorMap[type];
|
|
@@ -224,32 +221,6 @@ const Button = forwardRef((buttonProps, ref) => {
|
|
|
224
221
|
});
|
|
225
222
|
}
|
|
226
223
|
};
|
|
227
|
-
const setStayTimer = () => {
|
|
228
|
-
clearTimeout(refs.current.hoverStayTimer);
|
|
229
|
-
refs.current.hoverStayTimer = setTimeout(() => {
|
|
230
|
-
setIsHover(false);
|
|
231
|
-
clearTimeout(refs.current.hoverStayTimer);
|
|
232
|
-
}, hoverStayTime);
|
|
233
|
-
};
|
|
234
|
-
const setStartTimer = () => {
|
|
235
|
-
clearTimeout(refs.current.hoverStartTimer);
|
|
236
|
-
refs.current.hoverStartTimer = setTimeout(() => {
|
|
237
|
-
setIsHover(true);
|
|
238
|
-
clearTimeout(refs.current.hoverStartTimer);
|
|
239
|
-
}, hoverStartTime);
|
|
240
|
-
};
|
|
241
|
-
const onTouchStart = (evt) => {
|
|
242
|
-
bindtouchstart && bindtouchstart(evt);
|
|
243
|
-
if (disabled)
|
|
244
|
-
return;
|
|
245
|
-
setStartTimer();
|
|
246
|
-
};
|
|
247
|
-
const onTouchEnd = (evt) => {
|
|
248
|
-
bindtouchend && bindtouchend(evt);
|
|
249
|
-
if (disabled)
|
|
250
|
-
return;
|
|
251
|
-
setStayTimer();
|
|
252
|
-
};
|
|
253
224
|
const handleFormTypeFn = () => {
|
|
254
225
|
if (formType === 'submit') {
|
|
255
226
|
submitFn && submitFn();
|
|
@@ -269,8 +240,6 @@ const Button = forwardRef((buttonProps, ref) => {
|
|
|
269
240
|
ref: nodeRef,
|
|
270
241
|
style: extendObject({}, innerStyle, layoutStyle)
|
|
271
242
|
}, layoutProps, {
|
|
272
|
-
bindtouchstart: (bindtouchstart || !disabled) && onTouchStart,
|
|
273
|
-
bindtouchend: (bindtouchend || !disabled) && onTouchEnd,
|
|
274
243
|
bindtap: !disabled && onTap
|
|
275
244
|
}), [
|
|
276
245
|
'disabled',
|
|
@@ -288,12 +257,15 @@ const Button = forwardRef((buttonProps, ref) => {
|
|
|
288
257
|
layoutRef,
|
|
289
258
|
disableTap: disabled
|
|
290
259
|
});
|
|
291
|
-
|
|
260
|
+
const baseButton = createElement(View, innerProps, loading && createElement(Loading, { alone: !children }), wrapChildren(props, {
|
|
292
261
|
hasVarDec,
|
|
293
262
|
varContext: varContextRef.current,
|
|
294
263
|
textStyle,
|
|
295
264
|
textProps
|
|
296
265
|
}));
|
|
266
|
+
return enableHoverStyle
|
|
267
|
+
? createElement(GestureDetector, { gesture }, baseButton)
|
|
268
|
+
: baseButton;
|
|
297
269
|
});
|
|
298
270
|
Button.displayName = 'MpxButton';
|
|
299
271
|
export default Button;
|
|
@@ -268,14 +268,8 @@ const Image = forwardRef((props, ref) => {
|
|
|
268
268
|
], {
|
|
269
269
|
layoutRef
|
|
270
270
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
uri: src,
|
|
274
|
-
onLayout: onSvgLoad,
|
|
275
|
-
onError: binderror && onSvgError,
|
|
276
|
-
style: extendObject({ transformOrigin: 'top left' }, modeStyle)
|
|
277
|
-
})
|
|
278
|
-
: loaded && renderImage({
|
|
271
|
+
const createBaseImage = (innerProps = {}) => {
|
|
272
|
+
return renderImage(extendObject({
|
|
279
273
|
source: { uri: src },
|
|
280
274
|
resizeMode: resizeMode,
|
|
281
275
|
onLoad: bindload && onImageLoad,
|
|
@@ -285,7 +279,17 @@ const Image = forwardRef((props, ref) => {
|
|
|
285
279
|
width: isCropMode ? imageWidth : '100%',
|
|
286
280
|
height: isCropMode ? imageHeight : '100%'
|
|
287
281
|
}, isCropMode ? modeStyle : {})
|
|
288
|
-
}, enableFastImage)
|
|
282
|
+
}, innerProps), enableFastImage);
|
|
283
|
+
};
|
|
284
|
+
const SvgImage = createElement(View, innerProps, createElement(SvgCssUri, {
|
|
285
|
+
uri: src,
|
|
286
|
+
onLayout: onSvgLoad,
|
|
287
|
+
onError: binderror && onSvgError,
|
|
288
|
+
style: extendObject({ transformOrigin: 'top left' }, modeStyle)
|
|
289
|
+
}));
|
|
290
|
+
const BaseImage = createBaseImage(innerProps);
|
|
291
|
+
const LayoutImage = createElement(View, innerProps, loaded && createBaseImage());
|
|
292
|
+
return isSvg ? SvgImage : isLayoutMode ? LayoutImage : BaseImage;
|
|
289
293
|
});
|
|
290
294
|
Image.displayName = 'mpx-image';
|
|
291
295
|
export default Image;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { View, Text, Modal, TouchableWithoutFeedback } from 'react-native';
|
|
2
|
-
import
|
|
2
|
+
import Portal from '../mpx-portal';
|
|
3
|
+
import { PickerView } from '@ant-design/react-native';
|
|
3
4
|
import React, { forwardRef, useState, useRef, useEffect } from 'react';
|
|
4
5
|
import useNodesRef from '../useNodesRef'; // 引入辅助函数
|
|
5
6
|
// 可见应用窗口的大小。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react';
|
|
2
2
|
import { SafeAreaView, StyleSheet } from 'react-native';
|
|
3
3
|
import Reanimated, { useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated';
|
|
4
|
-
import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, isAndroid, useDebounceCallback, useStableCallback
|
|
4
|
+
import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, isAndroid, isIOS, useDebounceCallback, useStableCallback } from './utils';
|
|
5
5
|
import useNodesRef from './useNodesRef';
|
|
6
6
|
import PickerOverlay from './pickerViewOverlay';
|
|
7
7
|
import PickerMask from './pickerViewMask';
|
|
@@ -49,7 +49,7 @@ const _PickerViewColumn = forwardRef((props, ref) => {
|
|
|
49
49
|
return index * itemRawH;
|
|
50
50
|
}, [itemRawH]);
|
|
51
51
|
const stableResetScrollPosition = useStableCallback((y) => {
|
|
52
|
-
console.log('[mpx-picker-view-column], reset --->', 'columnIndex=', columnIndex, 'y=', y, touching.current, scrolling.current)
|
|
52
|
+
// console.log('[mpx-picker-view-column], reset --->', 'columnIndex=', columnIndex, 'y=', y, touching.current, scrolling.current, itemRawH, 'snapToOffsets=', snapToOffsets)
|
|
53
53
|
if (touching.current || scrolling.current) {
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
@@ -95,8 +95,9 @@ const _PickerViewColumn = forwardRef((props, ref) => {
|
|
|
95
95
|
};
|
|
96
96
|
const onItemLayout = (e) => {
|
|
97
97
|
const { height: rawH } = e.nativeEvent.layout;
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const roundedH = Math.round(rawH);
|
|
99
|
+
if (roundedH && roundedH !== itemRawH) {
|
|
100
|
+
setItemRawH(roundedH);
|
|
100
101
|
}
|
|
101
102
|
};
|
|
102
103
|
const onScrollBeginDrag = () => {
|
|
@@ -111,7 +112,7 @@ const _PickerViewColumn = forwardRef((props, ref) => {
|
|
|
111
112
|
touching.current = false;
|
|
112
113
|
const { y } = e.nativeEvent.contentOffset;
|
|
113
114
|
if (isIOS) {
|
|
114
|
-
if (y
|
|
115
|
+
if (y >= 0 && y <= snapToOffsets[maxIndex]) {
|
|
115
116
|
debounceResetScrollPosition(y);
|
|
116
117
|
}
|
|
117
118
|
}
|
|
@@ -123,25 +124,23 @@ const _PickerViewColumn = forwardRef((props, ref) => {
|
|
|
123
124
|
const onMomentumScrollEnd = (e) => {
|
|
124
125
|
scrolling.current = false;
|
|
125
126
|
const { y: scrollY } = e.nativeEvent.contentOffset;
|
|
127
|
+
// console.log('[mpx-picker-view-column], onMomentumScrollEnd --->', 'columnIndex=', columnIndex, scrollY, itemRawH)
|
|
126
128
|
if (isIOS && scrollY % itemRawH !== 0) {
|
|
127
129
|
return debounceResetScrollPosition(scrollY);
|
|
128
130
|
}
|
|
129
131
|
const calcIndex = getIndex(scrollY);
|
|
130
|
-
activeIndex.current
|
|
131
|
-
|
|
132
|
+
if (calcIndex !== activeIndex.current) {
|
|
133
|
+
activeIndex.current = calcIndex;
|
|
132
134
|
onSelectChange(calcIndex);
|
|
133
135
|
}
|
|
134
136
|
};
|
|
135
137
|
const onScroll = (e) => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
// 全局注册的震动触感 hook
|
|
141
|
-
const pickerVibrate = global.__mpx.config.rnConfig.pickerVibrate;
|
|
138
|
+
// 全局注册的振动触感 hook
|
|
139
|
+
const pickerVibrate = global.__mpx?.config?.rnConfig?.pickerVibrate;
|
|
142
140
|
if (typeof pickerVibrate !== 'function') {
|
|
143
141
|
return;
|
|
144
142
|
}
|
|
143
|
+
const { y } = e.nativeEvent.contentOffset;
|
|
145
144
|
const { index: prevIndex, y: _y } = prevScrollingInfo.current;
|
|
146
145
|
if (touching.current || scrolling.current) {
|
|
147
146
|
if (Math.abs(y - _y) >= itemRawH) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { getFocusedNavigation } from '@mpxjs/utils';
|
|
3
|
+
const PortalConsumer = ({ manager, children }) => {
|
|
4
|
+
const keyRef = useRef(null);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const navigation = getFocusedNavigation();
|
|
7
|
+
const curPageId = navigation?.pageId;
|
|
8
|
+
manager.update(keyRef.current, children, curPageId);
|
|
9
|
+
}, [children]);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!manager) {
|
|
12
|
+
throw new Error('Looks like you forgot to wrap your root component with `Provider` component from `@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-portal`.\n\n');
|
|
13
|
+
}
|
|
14
|
+
const navigation = getFocusedNavigation();
|
|
15
|
+
const curPageId = navigation?.pageId;
|
|
16
|
+
keyRef.current = manager.mount(children, undefined, curPageId);
|
|
17
|
+
return () => {
|
|
18
|
+
manager.unmount(keyRef.current, curPageId);
|
|
19
|
+
};
|
|
20
|
+
}, []);
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
export default PortalConsumer;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { View, DeviceEventEmitter, NativeEventEmitter, StyleSheet } from 'react-native';
|
|
3
|
+
import PortalManager from './portal-manager';
|
|
4
|
+
import { getFocusedNavigation } from '@mpxjs/utils';
|
|
5
|
+
import { PortalContext } from '../context';
|
|
6
|
+
// events
|
|
7
|
+
const addType = 'MPX_RN_ADD_PORTAL';
|
|
8
|
+
const removeType = 'MPX_RN_REMOVE_PORTAL';
|
|
9
|
+
// fix react native web does not support DeviceEventEmitter
|
|
10
|
+
const TopViewEventEmitter = DeviceEventEmitter || new NativeEventEmitter();
|
|
11
|
+
const styles = StyleSheet.create({
|
|
12
|
+
container: {
|
|
13
|
+
flex: 1
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
class PortalGuard {
|
|
17
|
+
nextKey = 10000;
|
|
18
|
+
add = (e) => {
|
|
19
|
+
const key = this.nextKey++;
|
|
20
|
+
TopViewEventEmitter.emit(addType, e, key);
|
|
21
|
+
return key;
|
|
22
|
+
};
|
|
23
|
+
remove = (key) => {
|
|
24
|
+
TopViewEventEmitter.emit(removeType, key);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* portal
|
|
29
|
+
*/
|
|
30
|
+
export const portal = new PortalGuard();
|
|
31
|
+
const PortalHost = ({ children }) => {
|
|
32
|
+
const _nextKey = useRef(0);
|
|
33
|
+
const _queue = useRef([]);
|
|
34
|
+
const _addType = useRef(null);
|
|
35
|
+
const _removeType = useRef(null);
|
|
36
|
+
const manager = useRef(null);
|
|
37
|
+
let currentPageId;
|
|
38
|
+
const _mount = (children, _key, curPageId) => {
|
|
39
|
+
const navigation = getFocusedNavigation();
|
|
40
|
+
const pageId = navigation?.pageId;
|
|
41
|
+
if (pageId !== (curPageId ?? currentPageId)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const key = _key || _nextKey.current++;
|
|
45
|
+
if (manager.current) {
|
|
46
|
+
manager.current.mount(key, children);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
_queue.current.push({ type: 'mount', key, children });
|
|
50
|
+
}
|
|
51
|
+
return key;
|
|
52
|
+
};
|
|
53
|
+
const _unmount = (key, curPageId) => {
|
|
54
|
+
const navigation = getFocusedNavigation();
|
|
55
|
+
const pageId = navigation?.pageId;
|
|
56
|
+
if (pageId !== (curPageId ?? currentPageId)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (manager.current) {
|
|
60
|
+
manager.current.unmount(key);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
_queue.current.push({ type: 'unmount', key });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const _update = (key, children, curPageId) => {
|
|
67
|
+
const navigation = getFocusedNavigation();
|
|
68
|
+
const pageId = navigation?.pageId;
|
|
69
|
+
if (pageId !== (curPageId ?? currentPageId)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (manager.current) {
|
|
73
|
+
manager.current.update(key, children);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const op = { type: 'mount', key, children };
|
|
77
|
+
const index = _queue.current.findIndex((o) => o.type === 'mount' || (o.type === 'update' && o.key === key));
|
|
78
|
+
if (index > -1) {
|
|
79
|
+
_queue.current[index] = op;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
_queue.current.push(op);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const navigation = getFocusedNavigation();
|
|
88
|
+
currentPageId = navigation?.pageId;
|
|
89
|
+
_addType.current = TopViewEventEmitter.addListener(addType, _mount);
|
|
90
|
+
_removeType.current = TopViewEventEmitter.addListener(removeType, _unmount);
|
|
91
|
+
return () => {
|
|
92
|
+
while (_queue.current.length && manager.current) {
|
|
93
|
+
const action = _queue.current.pop();
|
|
94
|
+
if (!action) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// tslint:disable-next-line:switch-default
|
|
98
|
+
switch (action.type) {
|
|
99
|
+
case 'mount':
|
|
100
|
+
manager.current?.mount(action.key, action.children);
|
|
101
|
+
break;
|
|
102
|
+
case 'update':
|
|
103
|
+
manager.current?.update(action.key, action.children);
|
|
104
|
+
break;
|
|
105
|
+
case 'unmount':
|
|
106
|
+
manager.current?.unmount(action.key);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}, []);
|
|
112
|
+
return (<PortalContext.Provider value={{
|
|
113
|
+
mount: _mount,
|
|
114
|
+
update: _update,
|
|
115
|
+
unmount: _unmount
|
|
116
|
+
}}>
|
|
117
|
+
{/* Need collapsable=false here to clip the elevations, otherwise they appear above Portal components */}
|
|
118
|
+
<View style={styles.container} collapsable={false}>
|
|
119
|
+
{children}
|
|
120
|
+
</View>
|
|
121
|
+
<PortalManager ref={manager}/>
|
|
122
|
+
</PortalContext.Provider>);
|
|
123
|
+
};
|
|
124
|
+
export default PortalHost;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useState, useCallback, forwardRef, useImperativeHandle } from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
const _PortalManager = forwardRef((props, ref) => {
|
|
4
|
+
const [state, setState] = useState({
|
|
5
|
+
portals: []
|
|
6
|
+
});
|
|
7
|
+
const mount = useCallback((key, children) => {
|
|
8
|
+
setState((prevState) => ({
|
|
9
|
+
portals: [...prevState.portals, { key, children }]
|
|
10
|
+
}));
|
|
11
|
+
}, [state]);
|
|
12
|
+
const update = useCallback((key, children) => {
|
|
13
|
+
setState((prevState) => ({
|
|
14
|
+
portals: prevState.portals.map((item) => {
|
|
15
|
+
if (item.key === key) {
|
|
16
|
+
return { ...item, children };
|
|
17
|
+
}
|
|
18
|
+
return item;
|
|
19
|
+
})
|
|
20
|
+
}));
|
|
21
|
+
}, [state]);
|
|
22
|
+
const unmount = useCallback((key) => {
|
|
23
|
+
setState((prevState) => ({
|
|
24
|
+
portals: prevState.portals.filter((item) => item.key !== key)
|
|
25
|
+
}));
|
|
26
|
+
}, []);
|
|
27
|
+
useImperativeHandle(ref, () => ({
|
|
28
|
+
mount,
|
|
29
|
+
update,
|
|
30
|
+
unmount,
|
|
31
|
+
portals: state.portals
|
|
32
|
+
}));
|
|
33
|
+
return (<>
|
|
34
|
+
{state.portals.map(({ key, children }, i) => (<View key={key} collapsable={false} // Need collapsable=false here to clip the elevations
|
|
35
|
+
pointerEvents="box-none" style={[StyleSheet.absoluteFill, { zIndex: 1000 + i }]}>
|
|
36
|
+
{children}
|
|
37
|
+
</View>))}
|
|
38
|
+
</>);
|
|
39
|
+
});
|
|
40
|
+
export default _PortalManager;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PortalContext } from './context';
|
|
2
|
+
import PortalConsumer from './mpx-portal/portal-consumer';
|
|
3
|
+
import PortalHost, { portal } from './mpx-portal/portal-host';
|
|
4
|
+
const Portal = ({ children }) => {
|
|
5
|
+
return (<PortalContext.Consumer>
|
|
6
|
+
{(manager) => (<PortalConsumer manager={manager}>{children}</PortalConsumer>)}
|
|
7
|
+
</PortalContext.Consumer>);
|
|
8
|
+
};
|
|
9
|
+
Portal.Host = PortalHost;
|
|
10
|
+
Portal.add = portal.add;
|
|
11
|
+
Portal.remove = portal.remove;
|
|
12
|
+
export default Portal;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createContext, useMemo } from 'react';
|
|
2
|
+
import LocaleProvider from './locale-provider';
|
|
3
|
+
import Portal from './mpx-portal';
|
|
4
|
+
import { extendObject } from './utils';
|
|
5
|
+
const defaultTheme = {
|
|
6
|
+
color_text_base: '#000000',
|
|
7
|
+
color_text_base_inverse: '#ffffff',
|
|
8
|
+
color_text_secondary: '#a4a9b0',
|
|
9
|
+
color_text_placeholder: '#bbbbbb',
|
|
10
|
+
color_text_disabled: '#bbbbbb',
|
|
11
|
+
color_text_caption: '#888888',
|
|
12
|
+
color_text_paragraph: '#333333',
|
|
13
|
+
color_error: '#ff4d4f',
|
|
14
|
+
color_warning: '#faad14',
|
|
15
|
+
color_success: '#52c41a',
|
|
16
|
+
color_primary: '#1677ff'
|
|
17
|
+
};
|
|
18
|
+
export const ThemeContext = createContext(defaultTheme);
|
|
19
|
+
const ThemeProvider = (props) => {
|
|
20
|
+
const { value, children } = props;
|
|
21
|
+
const theme = useMemo(() => (extendObject({}, defaultTheme, value)), [value]);
|
|
22
|
+
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
|
|
23
|
+
};
|
|
24
|
+
const Provider = ({ locale, theme, children }) => {
|
|
25
|
+
return (<LocaleProvider locale={locale}>
|
|
26
|
+
<ThemeProvider value={theme}>
|
|
27
|
+
<Portal.Host>{children}</Portal.Host>
|
|
28
|
+
</ThemeProvider>
|
|
29
|
+
</LocaleProvider>);
|
|
30
|
+
};
|
|
31
|
+
export default Provider;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ✔ enable
|
|
3
3
|
*/
|
|
4
4
|
import { createElement, Fragment } from 'react';
|
|
5
|
-
import
|
|
5
|
+
import Portal from './mpx-portal';
|
|
6
6
|
import { warn } from '@mpxjs/utils';
|
|
7
7
|
const _RootPortal = (props) => {
|
|
8
8
|
const { children, enable = true } = props;
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* ✔ bindscroll
|
|
33
33
|
*/
|
|
34
34
|
import { ScrollView } from 'react-native-gesture-handler';
|
|
35
|
-
import { RefreshControl
|
|
35
|
+
import { RefreshControl } from 'react-native';
|
|
36
36
|
import { useRef, useState, useEffect, forwardRef, useContext, createElement, useMemo } from 'react';
|
|
37
37
|
import { useAnimatedRef } from 'react-native-reanimated';
|
|
38
38
|
import { warn } from '@mpxjs/utils';
|
|
@@ -184,7 +184,6 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
184
184
|
visibleLength
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
|
-
const observerTimers = {};
|
|
188
187
|
function onScroll(e) {
|
|
189
188
|
const { bindscroll } = props;
|
|
190
189
|
const { x: scrollLeft, y: scrollTop } = e.nativeEvent.contentOffset;
|
|
@@ -202,20 +201,6 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
202
201
|
layoutRef
|
|
203
202
|
}, props));
|
|
204
203
|
updateScrollOptions(e, { scrollLeft, scrollTop });
|
|
205
|
-
if (enableTriggerIntersectionObserver && intersectionObservers) {
|
|
206
|
-
for (const key in intersectionObservers) {
|
|
207
|
-
if (Platform.OS === 'android') {
|
|
208
|
-
intersectionObservers[key].throttleMeasure();
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
// TODO: 由于iOS在onScroll滚动的过程中view的计算measureInWindow计算的值不发生变化,所以暂时在ios上加一个延时计算
|
|
212
|
-
observerTimers[key] && clearTimeout(observerTimers[key]);
|
|
213
|
-
observerTimers[key] = setTimeout(() => {
|
|
214
|
-
intersectionObservers[key].throttleMeasure();
|
|
215
|
-
}, 300);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
204
|
}
|
|
220
205
|
function onScrollEnd(e) {
|
|
221
206
|
const { bindscrollend } = props;
|
|
@@ -234,6 +219,14 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
234
219
|
updateScrollOptions(e, { scrollLeft, scrollTop });
|
|
235
220
|
onStartReached(e);
|
|
236
221
|
onEndReached(e);
|
|
222
|
+
updateIntersection();
|
|
223
|
+
}
|
|
224
|
+
function updateIntersection() {
|
|
225
|
+
if (enableTriggerIntersectionObserver && intersectionObservers) {
|
|
226
|
+
for (const key in intersectionObservers) {
|
|
227
|
+
intersectionObservers[key].throttleMeasure();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
237
230
|
}
|
|
238
231
|
function scrollToOffset(x = 0, y = 0) {
|
|
239
232
|
if (scrollViewRef.current) {
|
|
@@ -292,6 +285,7 @@ const _ScrollView = forwardRef((scrollViewProps = {}, ref) => {
|
|
|
292
285
|
function onScrollDrag(e) {
|
|
293
286
|
const { x: scrollLeft, y: scrollTop } = e.nativeEvent.contentOffset;
|
|
294
287
|
updateScrollOptions(e, { scrollLeft, scrollTop });
|
|
288
|
+
updateIntersection();
|
|
295
289
|
}
|
|
296
290
|
const scrollAdditionalProps = extendObject({
|
|
297
291
|
style: extendObject({}, innerStyle, layoutStyle),
|
|
@@ -361,8 +361,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
361
361
|
let isCriticalItem = false;
|
|
362
362
|
// 真实滚动到的偏移量坐标
|
|
363
363
|
let moveToTargetPos = 0;
|
|
364
|
-
|
|
365
|
-
const currentOffset = offset.value - previousMargin;
|
|
364
|
+
const currentOffset = translation < 0 ? offset.value - previousMargin : offset.value + previousMargin;
|
|
366
365
|
const computedIndex = Math.abs(currentOffset) / step.value;
|
|
367
366
|
const moveToIndex = translation < 0 ? Math.ceil(computedIndex) : Math.floor(computedIndex);
|
|
368
367
|
// 实际应该定位的索引值
|
|
@@ -442,10 +441,13 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
442
441
|
'worklet';
|
|
443
442
|
const { translation } = eventData;
|
|
444
443
|
// 向右滑动的back:trans < 0, 向左滑动的back: trans < 0
|
|
445
|
-
|
|
444
|
+
let currentOffset = Math.abs(offset.value);
|
|
445
|
+
if (props.circular) {
|
|
446
|
+
currentOffset += translation < 0 ? previousMargin : -previousMargin;
|
|
447
|
+
}
|
|
446
448
|
const curIndex = currentOffset / step.value;
|
|
447
449
|
const moveToIndex = (translation < 0 ? Math.floor(curIndex) : Math.ceil(curIndex)) - patchElementNum;
|
|
448
|
-
const targetOffset = -(moveToIndex + patchElementNum) * step.value + (
|
|
450
|
+
const targetOffset = -(moveToIndex + patchElementNum) * step.value + (props.circular ? -previousMargin : 0);
|
|
449
451
|
offset.value = withTiming(targetOffset, {
|
|
450
452
|
duration: easeDuration,
|
|
451
453
|
easing: easeMap[easeingFunc]
|
|
@@ -459,7 +461,10 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
459
461
|
function handleLongPress(eventData) {
|
|
460
462
|
'worklet';
|
|
461
463
|
const { translation } = eventData;
|
|
462
|
-
|
|
464
|
+
let currentOffset = Math.abs(offset.value);
|
|
465
|
+
if (props.circular) {
|
|
466
|
+
currentOffset += previousMargin;
|
|
467
|
+
}
|
|
463
468
|
const half = currentOffset % step.value > step.value / 2;
|
|
464
469
|
// 向右trans < 0, 向左trans > 0
|
|
465
470
|
const isExceedHalf = translation < 0 ? half : !half;
|