@mpxjs/webpack-plugin 2.9.69-beta.1 → 2.9.69-beta.11

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.
Files changed (51) hide show
  1. package/lib/config.js +3 -1
  2. package/lib/platform/template/wx/index.js +3 -1
  3. package/lib/react/processScript.js +6 -4
  4. package/lib/runtime/components/react/context.ts +17 -0
  5. package/lib/runtime/components/react/dist/context.js +2 -0
  6. package/lib/runtime/components/react/dist/getInnerListeners.js +2 -2
  7. package/lib/runtime/components/react/dist/locale-provider.jsx +15 -0
  8. package/lib/runtime/components/react/dist/mpx-button.jsx +16 -44
  9. package/lib/runtime/components/react/dist/mpx-image.jsx +13 -9
  10. package/lib/runtime/components/react/dist/mpx-picker/time.jsx +2 -1
  11. package/lib/runtime/components/react/dist/mpx-picker-view-column-item.jsx +2 -2
  12. package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +65 -61
  13. package/lib/runtime/components/react/dist/mpx-portal/portal-consumer.jsx +23 -0
  14. package/lib/runtime/components/react/dist/mpx-portal/portal-host.jsx +93 -0
  15. package/lib/runtime/components/react/dist/mpx-portal/portal-manager.jsx +40 -0
  16. package/lib/runtime/components/react/dist/mpx-portal.jsx +17 -0
  17. package/lib/runtime/components/react/dist/mpx-provider.jsx +31 -0
  18. package/lib/runtime/components/react/dist/mpx-root-portal.jsx +5 -3
  19. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +9 -5
  20. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +13 -7
  21. package/lib/runtime/components/react/dist/mpx-swiper.jsx +384 -321
  22. package/lib/runtime/components/react/dist/mpx-view.jsx +16 -20
  23. package/lib/runtime/components/react/dist/mpx-web-view.jsx +129 -53
  24. package/lib/runtime/components/react/dist/pickerFaces.js +2 -7
  25. package/lib/runtime/components/react/dist/useAnimationHooks.js +30 -13
  26. package/lib/runtime/components/react/dist/utils.jsx +60 -2
  27. package/lib/runtime/components/react/getInnerListeners.ts +2 -2
  28. package/lib/runtime/components/react/locale-provider.tsx +83 -0
  29. package/lib/runtime/components/react/mpx-button.tsx +20 -57
  30. package/lib/runtime/components/react/mpx-image.tsx +41 -25
  31. package/lib/runtime/components/react/mpx-picker/time.tsx +2 -1
  32. package/lib/runtime/components/react/mpx-picker-view-column-item.tsx +3 -3
  33. package/lib/runtime/components/react/mpx-picker-view-column.tsx +70 -69
  34. package/lib/runtime/components/react/mpx-portal/portal-consumer.tsx +32 -0
  35. package/lib/runtime/components/react/mpx-portal/portal-host.tsx +127 -0
  36. package/lib/runtime/components/react/mpx-portal/portal-manager.tsx +64 -0
  37. package/lib/runtime/components/react/mpx-portal.tsx +35 -0
  38. package/lib/runtime/components/react/mpx-provider.tsx +51 -0
  39. package/lib/runtime/components/react/mpx-root-portal.tsx +5 -3
  40. package/lib/runtime/components/react/mpx-scroll-view.tsx +10 -8
  41. package/lib/runtime/components/react/mpx-swiper-item.tsx +13 -7
  42. package/lib/runtime/components/react/mpx-swiper.tsx +378 -325
  43. package/lib/runtime/components/react/mpx-view.tsx +19 -22
  44. package/lib/runtime/components/react/mpx-web-view.tsx +170 -62
  45. package/lib/runtime/components/react/pickerFaces.ts +2 -7
  46. package/lib/runtime/components/react/types/global.d.ts +7 -0
  47. package/lib/runtime/components/react/useAnimationHooks.ts +34 -14
  48. package/lib/runtime/components/react/utils.tsx +67 -2
  49. package/lib/template-compiler/compiler.js +1 -1
  50. package/lib/wxss/loader.js +15 -2
  51. package/package.json +1 -1
package/lib/config.js CHANGED
@@ -138,7 +138,9 @@ module.exports = {
138
138
  }
139
139
  },
140
140
  getEvent (eventName, prefix = 'on') {
141
- return dash2hump(prefix + '-' + eventName)
141
+ return prefix + dash2hump(eventName.replace(/^./, (matched) => {
142
+ return matched.toUpperCase()
143
+ }))
142
144
  },
143
145
  defaultModelProp: 'value',
144
146
  defaultModelEvent: 'input',
@@ -302,7 +302,9 @@ module.exports = function getSpec ({ warn, error }) {
302
302
  const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'ali' })
303
303
  const rEventName = runRules(eventRules, eventName, { mode: 'ali' })
304
304
  return {
305
- name: dash2hump(rPrefix + '-' + rEventName) + modifierStr,
305
+ name: rPrefix + dash2hump(rEventName.replace(/^./, (matched) => {
306
+ return matched.toUpperCase()
307
+ })) + modifierStr,
306
308
  value
307
309
  }
308
310
  },
@@ -14,25 +14,27 @@ module.exports = function (script, {
14
14
  localPagesMap
15
15
  }, callback) {
16
16
  let scriptSrcMode = srcMode
17
+ const mode = loaderContext.getMpx().mode
17
18
  if (script) {
18
19
  scriptSrcMode = script.mode || scriptSrcMode
19
20
  } else {
20
21
  script = { tag: 'script' }
21
22
  }
22
-
23
23
  let output = '/* script */\n'
24
24
  if (ctorType === 'app') {
25
25
  output += `
26
26
  import { getComponent } from ${stringifyRequest(loaderContext, optionProcessorPath)}
27
27
  import { NavigationContainer, StackActions } from '@react-navigation/native'
28
- import { createStackNavigator } from '@react-navigation/stack'
29
- import { Provider } from '@ant-design/react-native'
28
+ ${mode === 'ios' ? "import { createNativeStackNavigator } from '@react-navigation/native-stack'" : "import { createStackNavigator } from '@react-navigation/stack'" }
29
+ import { useHeaderHeight } from '@react-navigation/elements';
30
+ import Provider from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-provider'
30
31
  import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
31
32
  import { GestureHandlerRootView } from 'react-native-gesture-handler'
32
33
 
33
34
  global.__navigationHelper = {
34
35
  NavigationContainer: NavigationContainer,
35
- createStackNavigator: createStackNavigator,
36
+ createStackNavigator: ${mode === 'ios' ? 'createNativeStackNavigator' : 'createStackNavigator'},
37
+ useHeaderHeight: useHeaderHeight,
36
38
  StackActions: StackActions,
37
39
  GestureHandlerRootView: GestureHandlerRootView,
38
40
  Provider: Provider,
@@ -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) => 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) > 1 ||
86
- Math.abs(currentPageY - tapDetailInfo.y) > 1) {
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, useState, forwardRef, useContext } from 'react';
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 { getCurrentPage, splitProps, splitStyle, useLayout, useTransformStyle, wrapChildren, extendObject } from './utils';
40
+ import { GestureDetector } from 'react-native-gesture-handler';
41
+ import { getCurrentPage, splitProps, splitStyle, useLayout, useTransformStyle, wrapChildren, extendObject, useHover } from './utils';
41
42
  import useInnerProps, { getCustomEvent } from './getInnerListeners';
42
43
  import useNodesRef from './useNodesRef';
43
44
  import { RouteContext, FormContext } from './context';
@@ -128,38 +129,34 @@ 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, bindtouchstart, bindtouchend } = props;
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 enableHover = hoverClass !== 'none';
136
+ const { isHover, gesture } = useHover({ enableHover, hoverStartTime, hoverStayTime, disabled });
134
137
  let submitFn;
135
138
  let resetFn;
136
139
  if (formContext) {
137
140
  submitFn = formContext.submit;
138
141
  resetFn = formContext.reset;
139
142
  }
140
- const refs = useRef({
141
- hoverStartTimer: undefined,
142
- hoverStayTimer: undefined
143
- });
144
- const [isHover, setIsHover] = useState(false);
145
143
  const isMiniSize = size === 'mini';
146
- const applyHoverEffect = isHover && hoverClass !== 'none';
147
144
  const [color, hoverColor, plainColor, disabledColor] = TypeColorMap[type];
148
- const normalBackgroundColor = disabled ? disabledColor : applyHoverEffect || loading ? hoverColor : color;
145
+ const normalBackgroundColor = disabled ? disabledColor : isHover || loading ? hoverColor : color;
149
146
  const plainBorderColor = disabled
150
147
  ? 'rgba(0, 0, 0, .2)'
151
- : applyHoverEffect
148
+ : isHover
152
149
  ? `rgba(${plainColor},.6)`
153
150
  : `rgb(${plainColor})`;
154
151
  const normalBorderColor = type === 'default' ? 'rgba(0, 0, 0, .2)' : normalBackgroundColor;
155
152
  const plainTextColor = disabled
156
153
  ? 'rgba(0, 0, 0, .2)'
157
- : applyHoverEffect
154
+ : isHover
158
155
  ? `rgba(${plainColor}, .6)`
159
156
  : `rgb(${plainColor})`;
160
157
  const normalTextColor = type === 'default'
161
- ? `rgba(0, 0, 0, ${disabled ? 0.3 : applyHoverEffect || loading ? 0.6 : 1})`
162
- : `rgba(255 ,255 ,255 , ${disabled || applyHoverEffect || loading ? 0.6 : 1})`;
158
+ ? `rgba(0, 0, 0, ${disabled ? 0.3 : isHover || loading ? 0.6 : 1})`
159
+ : `rgba(255 ,255 ,255 , ${disabled || isHover || loading ? 0.6 : 1})`;
163
160
  const viewStyle = {
164
161
  borderWidth: 1,
165
162
  borderStyle: 'solid',
@@ -169,7 +166,7 @@ const Button = forwardRef((buttonProps, ref) => {
169
166
  const defaultViewStyle = extendObject({}, styles.button, isMiniSize ? styles.buttonMini : null, viewStyle);
170
167
  const defaultTextStyle = extendObject({}, styles.text, isMiniSize ? styles.textMini : {}, { color: plain ? plainTextColor : normalTextColor });
171
168
  const defaultStyle = extendObject({}, defaultViewStyle, defaultTextStyle);
172
- const styleObj = extendObject({}, defaultStyle, style, applyHoverEffect ? hoverStyle : {});
169
+ const styleObj = extendObject({}, defaultStyle, style, isHover ? hoverStyle : {});
173
170
  const { hasSelfPercent, normalStyle, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
174
171
  const nodeRef = useRef(null);
175
172
  useNodesRef(props, ref, nodeRef, { style: normalStyle });
@@ -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
- return createElement(View, innerProps, loading && createElement(Loading, { alone: !children }), wrapChildren(props, {
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 enableHover
267
+ ? createElement(GestureDetector, { gesture: 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
- return createElement(View, innerProps, isSvg
272
- ? createElement(SvgCssUri, {
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 { PickerView, Portal } from '@ant-design/react-native';
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
  // 可见应用窗口的大小。
@@ -3,7 +3,7 @@ import Reanimated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValu
3
3
  import { wrapChildren, extendObject } from './utils';
4
4
  import { createFaces } from './pickerFaces';
5
5
  import { usePickerViewColumnAnimationContext } from './pickerVIewContext';
6
- const _PickerViewColumnItem = ({ item, index, itemHeight, itemWidth, textStyleFromParent, textStyle, hasVarDec, varContext, textProps, visibleCount, onItemLayout }) => {
6
+ const _PickerViewColumnItem = ({ item, index, itemHeight, itemWidth = '100%', textStyleFromParent, textStyle, hasVarDec, varContext, textProps, visibleCount, onItemLayout }) => {
7
7
  const offsetYShared = usePickerViewColumnAnimationContext();
8
8
  const facesShared = useSharedValue(createFaces(itemHeight, visibleCount));
9
9
  useEffect(() => {
@@ -14,8 +14,8 @@ const _PickerViewColumnItem = ({ item, index, itemHeight, itemWidth, textStyleFr
14
14
  return {
15
15
  opacity: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.opacity), Extrapolation.CLAMP),
16
16
  transform: [
17
- { rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.CLAMP) + 'deg' },
18
17
  { translateY: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.offsetY), Extrapolation.EXTEND) },
18
+ { rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.CLAMP) + 'deg' },
19
19
  { scale: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.scale), Extrapolation.EXTEND) }
20
20
  ]
21
21
  };
@@ -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 } from './utils';
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';
@@ -20,14 +20,12 @@ const _PickerViewColumn = forwardRef((props, ref) => {
20
20
  style: normalStyle
21
21
  });
22
22
  const { height: pickerH, itemHeight } = wrapperStyle;
23
- const [scrollViewWidth, setScrollViewWidth] = useState('100%');
24
- const [itemRawW, setItemRawW] = useState('100%');
25
23
  const [itemRawH, setItemRawH] = useState(itemHeight);
26
24
  const maxIndex = useMemo(() => columnData.length - 1, [columnData]);
27
- const maxScrollViewWidth = useRef(-1);
28
25
  const prevScrollingInfo = useRef({ index: initialIndex, y: 0 });
29
26
  const touching = useRef(false);
30
27
  const scrolling = useRef(false);
28
+ const timerScrollTo = useRef(null);
31
29
  const activeIndex = useRef(initialIndex);
32
30
  const prevIndex = usePrevious(initialIndex);
33
31
  const prevMaxIndex = usePrevious(maxIndex);
@@ -39,10 +37,6 @@ const _PickerViewColumn = forwardRef((props, ref) => {
39
37
  nodeRef: scrollViewRef
40
38
  });
41
39
  // console.log('[mpx-picker-view-column], render ---> columnIndex=', columnIndex, 'initialIndex=', initialIndex, 'columnData=', columnData.length, 'pickerH=', pickerH, 'itemRawH=', itemRawH, 'itemHeight=', itemHeight)
42
- // const initialOffset = useMemo(() => ({
43
- // x: 0,
44
- // y: itemRawH * initialIndex
45
- // }), [itemRawH])
46
40
  const paddingHeight = useMemo(() => Math.round((pickerH - itemHeight) / 2), [pickerH, itemHeight]);
47
41
  const snapToOffsets = useMemo(() => columnData.map((_, i) => i * itemRawH), [columnData, itemRawH]);
48
42
  const contentContainerStyle = useMemo(() => {
@@ -52,8 +46,38 @@ const _PickerViewColumn = forwardRef((props, ref) => {
52
46
  const calc = Math.round(y / itemRawH);
53
47
  return Math.max(0, Math.min(calc, maxIndex));
54
48
  }, [itemRawH, maxIndex]);
49
+ const getYofIndex = useCallback((index) => {
50
+ return index * itemRawH;
51
+ }, [itemRawH]);
52
+ const stableResetScrollPosition = useStableCallback((y) => {
53
+ // console.log('[mpx-picker-view-column], reset --->', 'columnIndex=', columnIndex, 'y=', y, touching.current, scrolling.current, itemRawH, 'snapToOffsets=', snapToOffsets)
54
+ if (touching.current || scrolling.current) {
55
+ return;
56
+ }
57
+ // needReset.current = true
58
+ if (y % itemRawH !== 0) {
59
+ scrolling.current = true;
60
+ const targetIndex = getIndex(y);
61
+ const targetY = getYofIndex(targetIndex);
62
+ scrollViewRef.current?.scrollTo({ x: 0, y: targetY, animated: false });
63
+ }
64
+ else {
65
+ onMomentumScrollEnd({ nativeEvent: { contentOffset: { y } } });
66
+ }
67
+ });
68
+ const debounceResetScrollPosition = useDebounceCallback(stableResetScrollPosition, 10);
69
+ const clearTimerScrollTo = () => {
70
+ if (timerScrollTo.current) {
71
+ clearTimeout(timerScrollTo.current);
72
+ timerScrollTo.current = null;
73
+ }
74
+ };
75
+ useEffect(() => {
76
+ return () => {
77
+ clearTimerScrollTo();
78
+ };
79
+ }, []);
55
80
  useEffect(() => {
56
- // console.log('[mpx-picker-view-column], useEffect000 --->', 'columnIndex=', columnIndex, 'initialIndex=', initialIndex, 'prevIndex=', prevIndex, 'activeIndex=', activeIndex.current, 'maxIndex=', maxIndex, 'prevMaxIndex=', prevMaxIndex)
57
81
  if (!scrollViewRef.current ||
58
82
  !itemRawH ||
59
83
  touching.current ||
@@ -64,89 +88,69 @@ const _PickerViewColumn = forwardRef((props, ref) => {
64
88
  maxIndex !== prevMaxIndex) {
65
89
  return;
66
90
  }
67
- // console.log('[mpx-picker-view-column], useEffect ---> columnIndex=', columnIndex, 'initialIndex=', initialIndex, 'y=', itemRawH * initialIndex, `${scrollViewRef.current?.scrollTo}`)
68
- setTimeout(() => {
91
+ clearTimerScrollTo();
92
+ timerScrollTo.current = setTimeout(() => {
69
93
  scrollViewRef.current?.scrollTo({
70
94
  x: 0,
71
- y: itemRawH * initialIndex,
95
+ y: getYofIndex(initialIndex),
72
96
  animated: false
73
97
  });
74
98
  }, isAndroid ? 200 : 0);
75
99
  activeIndex.current = initialIndex;
76
100
  }, [itemRawH, initialIndex]);
77
101
  const onContentSizeChange = (_w, h) => {
78
- const y = itemRawH * initialIndex;
79
- // console.log('[mpx-picker-view-column], onContentSizeChange --->', 'columnIndex=', columnIndex, '_w=', _w, 'h=', h, 'y=', y, 'itemRawH=', itemRawH)
102
+ const y = getYofIndex(initialIndex);
80
103
  if (y <= h) {
81
- setTimeout(() => {
104
+ clearTimerScrollTo();
105
+ timerScrollTo.current = setTimeout(() => {
82
106
  scrollViewRef.current?.scrollTo({ x: 0, y, animated: false });
83
107
  }, 0);
84
108
  }
85
109
  };
86
- const onScrollViewLayout = (e) => {
87
- if (isAndroid) {
88
- return;
89
- }
90
- // RN iOS bug: https://github.com/facebook/react-native/issues/36135
91
- const { width } = e.nativeEvent.layout;
92
- const widthInt = Math.ceil(width);
93
- // console.log('[mpx-picker-view-column], onScrollViewLayout --->', 'columnIndex=', columnIndex, 'width=', width, 'widthInt=', widthInt, 'scrollViewWidth=', scrollViewWidth)
94
- if (widthInt !== scrollViewWidth) {
95
- const maxW = maxScrollViewWidth.current;
96
- if (maxW !== -1 && widthInt > maxW) {
97
- return;
98
- }
99
- if (maxW === -1) {
100
- maxScrollViewWidth.current = Math.ceil(widthInt * 1.5);
101
- }
102
- setScrollViewWidth(widthInt);
103
- }
104
- if (itemRawW === '100%') {
105
- setItemRawW(widthInt);
106
- }
107
- };
108
110
  const onItemLayout = (e) => {
109
111
  const { height: rawH } = e.nativeEvent.layout;
110
- // console.log('[mpx-picker-view-column], onItemLayout --->', 'columnIndex=', columnIndex, 'width=', width)
111
- if (rawH && itemRawH !== rawH) {
112
- setItemRawH(rawH);
112
+ const roundedH = Math.round(rawH);
113
+ if (roundedH && roundedH !== itemRawH) {
114
+ setItemRawH(roundedH);
113
115
  }
114
116
  };
115
- const onTouchStart = () => {
117
+ const onScrollBeginDrag = () => {
118
+ isIOS && debounceResetScrollPosition.clear();
116
119
  touching.current = true;
117
120
  prevScrollingInfo.current = {
118
121
  index: activeIndex.current,
119
- y: activeIndex.current * itemRawH
122
+ y: getYofIndex(activeIndex.current)
120
123
  };
121
124
  };
122
- const onTouchEnd = () => {
123
- touching.current = false;
124
- };
125
- const onTouchCancel = () => {
125
+ const onScrollEndDrag = (e) => {
126
126
  touching.current = false;
127
+ const { y } = e.nativeEvent.contentOffset;
128
+ if (isIOS) {
129
+ if (y >= 0 && y <= snapToOffsets[maxIndex]) {
130
+ debounceResetScrollPosition(y);
131
+ }
132
+ }
127
133
  };
128
134
  const onMomentumScrollBegin = () => {
135
+ isIOS && debounceResetScrollPosition.clear();
129
136
  scrolling.current = true;
130
137
  };
131
138
  const onMomentumScrollEnd = (e) => {
132
139
  scrolling.current = false;
133
- if (!itemRawH) {
134
- return;
135
- }
136
140
  const { y: scrollY } = e.nativeEvent.contentOffset;
137
- let calcIndex = Math.round(scrollY / itemRawH);
138
- activeIndex.current = calcIndex;
139
- if (calcIndex !== initialIndex) {
140
- calcIndex = Math.max(0, Math.min(calcIndex, maxIndex)) || 0;
141
+ // console.log('[mpx-picker-view-column], onMomentumScrollEnd --->', 'columnIndex=', columnIndex, scrollY, itemRawH)
142
+ if (isIOS && scrollY % itemRawH !== 0) {
143
+ return debounceResetScrollPosition(scrollY);
144
+ }
145
+ const calcIndex = getIndex(scrollY);
146
+ if (calcIndex !== activeIndex.current) {
147
+ activeIndex.current = calcIndex;
141
148
  onSelectChange(calcIndex);
142
149
  }
143
150
  };
144
151
  const onScroll = (e) => {
145
- if (isAndroid) {
146
- return;
147
- }
148
- // 全局注册的震动触感 hook
149
- const pickerVibrate = global.__mpx.config.rnConfig.pickerVibrate;
152
+ // 全局注册的振动触感 hook
153
+ const pickerVibrate = global.__mpx?.config?.rnConfig?.pickerVibrate;
150
154
  if (typeof pickerVibrate !== 'function') {
151
155
  return;
152
156
  }
@@ -158,7 +162,7 @@ const _PickerViewColumn = forwardRef((props, ref) => {
158
162
  if (currentId !== prevIndex) {
159
163
  prevScrollingInfo.current = {
160
164
  index: currentId,
161
- y: currentId * itemRawH
165
+ y: getYofIndex(currentId)
162
166
  };
163
167
  // vibrateShort({ type: 'selection' })
164
168
  pickerVibrate();
@@ -167,11 +171,11 @@ const _PickerViewColumn = forwardRef((props, ref) => {
167
171
  }
168
172
  };
169
173
  const renderInnerchild = () => columnData.map((item, index) => {
170
- return (<MpxPickerVIewColumnItem key={index} item={item} index={index} itemHeight={itemHeight} itemWidth={itemRawW} textStyleFromParent={textStyleFromParent} textStyle={textStyle} hasVarDec={hasVarDec} varContext={varContextRef.current} textProps={textProps} visibleCount={visibleCount} onItemLayout={onItemLayout}/>);
174
+ return (<MpxPickerVIewColumnItem key={index} item={item} index={index} itemHeight={itemHeight} textStyleFromParent={textStyleFromParent} textStyle={textStyle} hasVarDec={hasVarDec} varContext={varContextRef.current} textProps={textProps} visibleCount={visibleCount} onItemLayout={onItemLayout}/>);
171
175
  });
172
176
  const renderScollView = () => {
173
177
  return (<PickerViewColumnAnimationContext.Provider value={offsetYShared}>
174
- <Reanimated.ScrollView ref={scrollViewRef} bounces={true} horizontal={false} nestedScrollEnabled={true} removeClippedSubviews={false} showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} scrollEventThrottle={16} {...layoutProps} style={[{ width: scrollViewWidth }]} decelerationRate="fast" snapToOffsets={snapToOffsets} onScroll={onScroll} onLayout={onScrollViewLayout} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} onTouchCancel={onTouchCancel} onMomentumScrollBegin={onMomentumScrollBegin} onMomentumScrollEnd={onMomentumScrollEnd} onContentSizeChange={onContentSizeChange} contentContainerStyle={contentContainerStyle}>
178
+ <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}>
175
179
  {renderInnerchild()}
176
180
  </Reanimated.ScrollView>
177
181
  </PickerViewColumnAnimationContext.Provider>);
@@ -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);
19
+ };
20
+ }, []);
21
+ return null;
22
+ };
23
+ export default PortalConsumer;
@@ -0,0 +1,93 @@
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
+ const updateType = 'MPX_RN_UPDATE_PORTAL';
10
+ // fix react native web does not support DeviceEventEmitter
11
+ const TopViewEventEmitter = DeviceEventEmitter || new NativeEventEmitter();
12
+ const styles = StyleSheet.create({
13
+ container: {
14
+ flex: 1
15
+ }
16
+ });
17
+ class PortalGuard {
18
+ nextKey = 10000;
19
+ add = (e) => {
20
+ const key = this.nextKey++;
21
+ TopViewEventEmitter.emit(addType, e, key);
22
+ return key;
23
+ };
24
+ remove = (key) => {
25
+ TopViewEventEmitter.emit(removeType, key);
26
+ };
27
+ update = (key, e) => {
28
+ TopViewEventEmitter.emit(updateType, key, e);
29
+ };
30
+ }
31
+ /**
32
+ * portal
33
+ */
34
+ export const portal = new PortalGuard();
35
+ const PortalHost = ({ children }) => {
36
+ const _nextKey = useRef(0);
37
+ const _queue = useRef([]);
38
+ const _addType = useRef(null);
39
+ const _removeType = useRef(null);
40
+ const _updateType = useRef(null);
41
+ const manager = useRef(null);
42
+ let currentPageId;
43
+ const _mount = (children, _key, curPageId) => {
44
+ const navigation = getFocusedNavigation();
45
+ const pageId = navigation?.pageId;
46
+ if (pageId !== (curPageId ?? currentPageId)) {
47
+ return;
48
+ }
49
+ const key = _key || _nextKey.current++;
50
+ if (manager.current) {
51
+ manager.current.mount(key, children);
52
+ }
53
+ return key;
54
+ };
55
+ const _unmount = (key) => {
56
+ if (manager.current) {
57
+ manager.current.unmount(key);
58
+ }
59
+ };
60
+ const _update = (key, children, curPageId) => {
61
+ const navigation = getFocusedNavigation();
62
+ const pageId = navigation?.pageId;
63
+ if (pageId !== (curPageId ?? currentPageId)) {
64
+ return;
65
+ }
66
+ if (manager.current) {
67
+ manager.current.update(key, children);
68
+ }
69
+ };
70
+ useEffect(() => {
71
+ const navigation = getFocusedNavigation();
72
+ currentPageId = navigation?.pageId;
73
+ _addType.current = TopViewEventEmitter.addListener(addType, _mount);
74
+ _removeType.current = TopViewEventEmitter.addListener(removeType, _unmount);
75
+ _updateType.current = TopViewEventEmitter.addListener(updateType, _update);
76
+ return () => {
77
+ _addType.current?.remove();
78
+ _removeType.current?.remove();
79
+ _updateType.current?.remove();
80
+ };
81
+ }, []);
82
+ return (<PortalContext.Provider value={{
83
+ mount: _mount,
84
+ update: _update,
85
+ unmount: _unmount
86
+ }}>
87
+ <View style={styles.container} collapsable={false}>
88
+ {children}
89
+ </View>
90
+ <PortalManager ref={manager}/>
91
+ </PortalContext.Provider>);
92
+ };
93
+ export default PortalHost;