@mpxjs/webpack-plugin 2.9.70 → 2.9.72

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 (63) hide show
  1. package/lib/config.js +3 -1
  2. package/lib/platform/template/wx/component-config/movable-view.js +8 -1
  3. package/lib/platform/template/wx/component-config/picker-view.js +1 -5
  4. package/lib/platform/template/wx/component-config/scroll-view.js +1 -1
  5. package/lib/platform/template/wx/index.js +3 -5
  6. package/lib/react/processScript.js +2 -1
  7. package/lib/react/script-helper.js +5 -1
  8. package/lib/runtime/components/react/context.ts +8 -0
  9. package/lib/runtime/components/react/dist/context.js +2 -0
  10. package/lib/runtime/components/react/dist/getInnerListeners.js +34 -31
  11. package/lib/runtime/components/react/dist/mpx-button.jsx +16 -44
  12. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +93 -58
  13. package/lib/runtime/components/react/dist/mpx-navigator.jsx +1 -1
  14. package/lib/runtime/components/react/dist/mpx-picker-view-column-item.jsx +35 -0
  15. package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +151 -127
  16. package/lib/runtime/components/react/dist/mpx-picker-view.jsx +38 -34
  17. package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +10 -11
  18. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +11 -4
  19. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +31 -8
  20. package/lib/runtime/components/react/dist/mpx-swiper.jsx +670 -0
  21. package/lib/runtime/components/react/dist/mpx-view.jsx +15 -53
  22. package/lib/runtime/components/react/dist/pickerFaces.js +7 -6
  23. package/lib/runtime/components/react/dist/pickerVIewContext.js +14 -0
  24. package/lib/runtime/components/react/dist/pickerViewIndicator.jsx +23 -0
  25. package/lib/runtime/components/react/dist/pickerViewMask.jsx +18 -0
  26. package/lib/runtime/components/react/dist/useAnimationHooks.js +20 -2
  27. package/lib/runtime/components/react/dist/utils.jsx +74 -11
  28. package/lib/runtime/components/react/getInnerListeners.ts +43 -32
  29. package/lib/runtime/components/react/mpx-button.tsx +20 -57
  30. package/lib/runtime/components/react/mpx-movable-view.tsx +119 -74
  31. package/lib/runtime/components/react/mpx-navigator.tsx +1 -1
  32. package/lib/runtime/components/react/mpx-picker-view-column-item.tsx +76 -0
  33. package/lib/runtime/components/react/mpx-picker-view-column.tsx +206 -183
  34. package/lib/runtime/components/react/mpx-picker-view.tsx +49 -48
  35. package/lib/runtime/components/react/mpx-rich-text/index.tsx +12 -18
  36. package/lib/runtime/components/react/mpx-scroll-view.tsx +21 -10
  37. package/lib/runtime/components/react/mpx-swiper-item.tsx +45 -11
  38. package/lib/runtime/components/react/mpx-swiper.tsx +742 -0
  39. package/lib/runtime/components/react/mpx-view.tsx +18 -65
  40. package/lib/runtime/components/react/pickerFaces.ts +10 -7
  41. package/lib/runtime/components/react/pickerVIewContext.ts +27 -0
  42. package/lib/runtime/components/react/pickerViewIndicator.tsx +34 -0
  43. package/lib/runtime/components/react/pickerViewMask.tsx +30 -0
  44. package/lib/runtime/components/react/types/{getInnerListeners.ts → getInnerListeners.d.ts} +4 -5
  45. package/lib/runtime/components/react/types/global.d.ts +10 -0
  46. package/lib/runtime/components/react/useAnimationHooks.ts +24 -3
  47. package/lib/runtime/components/react/utils.tsx +85 -12
  48. package/lib/runtime/components/web/mpx-checkbox.vue +1 -1
  49. package/lib/runtime/components/web/mpx-picker-view-column.vue +9 -4
  50. package/lib/template-compiler/compiler.js +62 -14
  51. package/lib/wxss/loader.js +15 -2
  52. package/package.json +3 -3
  53. package/lib/runtime/components/react/dist/mpx-swiper/carouse.jsx +0 -480
  54. package/lib/runtime/components/react/dist/mpx-swiper/index.jsx +0 -68
  55. package/lib/runtime/components/react/dist/mpx-swiper/type.js +0 -1
  56. package/lib/runtime/components/react/dist/pickerOverlay.jsx +0 -21
  57. package/lib/runtime/components/react/dist/types/common.js +0 -1
  58. package/lib/runtime/components/react/dist/types/getInnerListeners.js +0 -1
  59. package/lib/runtime/components/react/mpx-swiper/carouse.tsx +0 -527
  60. package/lib/runtime/components/react/mpx-swiper/index.tsx +0 -80
  61. package/lib/runtime/components/react/mpx-swiper/type.ts +0 -87
  62. package/lib/runtime/components/react/pickerOverlay.tsx +0 -32
  63. /package/lib/runtime/components/react/types/{common.ts → common.d.ts} +0 -0
@@ -11,7 +11,7 @@
11
11
  * ✘ scale-min
12
12
  * ✘ scale-max
13
13
  * ✘ scale-value
14
- * animation
14
+ * animation
15
15
  * ✔ bindchange
16
16
  * ✘ bindscale
17
17
  * ✔ htouchmove
@@ -19,12 +19,13 @@
19
19
  */
20
20
  import { useEffect, forwardRef, useContext, useCallback, useRef, useMemo, createElement } from 'react';
21
21
  import { StyleSheet } from 'react-native';
22
- import { getCustomEvent } from './getInnerListeners';
22
+ import useInnerProps, { getCustomEvent } from './getInnerListeners';
23
23
  import useNodesRef from './useNodesRef';
24
24
  import { MovableAreaContext } from './context';
25
- import { useTransformStyle, splitProps, splitStyle, HIDDEN_STYLE, wrapChildren, flatGesture, extendObject } from './utils';
25
+ import { useTransformStyle, splitProps, splitStyle, HIDDEN_STYLE, wrapChildren, flatGesture, extendObject, omit } from './utils';
26
26
  import { GestureDetector, Gesture } from 'react-native-gesture-handler';
27
27
  import Animated, { useSharedValue, useAnimatedStyle, withDecay, runOnJS, runOnUI, useAnimatedReaction, withSpring } from 'react-native-reanimated';
28
+ import { collectDataset, noop } from '@mpxjs/utils';
28
29
  const styles = StyleSheet.create({
29
30
  container: {
30
31
  position: 'absolute',
@@ -233,8 +234,7 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
233
234
  });
234
235
  props.onLayout && props.onLayout(e);
235
236
  };
236
- const extendEvent = useCallback((e) => {
237
- 'worklet';
237
+ const extendEvent = useCallback((e, obj) => {
238
238
  const touchArr = [e.changedTouches, e.allTouches];
239
239
  touchArr.forEach(touches => {
240
240
  touches && touches.forEach((item) => {
@@ -242,45 +242,65 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
242
242
  item.pageY = item.absoluteY;
243
243
  });
244
244
  });
245
- e.touches = e.allTouches;
245
+ Object.assign(e, {
246
+ touches: e.allTouches,
247
+ detail: {
248
+ x: e.changedTouches[0].absoluteX,
249
+ y: e.changedTouches[0].absoluteY
250
+ },
251
+ currentTarget: {
252
+ id: props.id || '',
253
+ dataset: collectDataset(props),
254
+ offsetLeft: 0,
255
+ offsetTop: 0
256
+ }
257
+ }, obj);
246
258
  }, []);
259
+ const triggerStartOnJS = ({ e }) => {
260
+ extendEvent(e);
261
+ bindtouchstart && bindtouchstart(e);
262
+ catchtouchstart && catchtouchstart(e);
263
+ };
264
+ const triggerMoveOnJS = ({ e, hasTouchmove, hasCatchTouchmove, touchEvent }) => {
265
+ extendEvent(e);
266
+ if (hasTouchmove) {
267
+ if (touchEvent === 'htouchmove') {
268
+ bindhtouchmove && bindhtouchmove(e);
269
+ }
270
+ else if (touchEvent === 'vtouchmove') {
271
+ bindvtouchmove && bindvtouchmove(e);
272
+ }
273
+ bindtouchmove && bindtouchmove(e);
274
+ }
275
+ if (hasCatchTouchmove) {
276
+ if (touchEvent === 'htouchmove') {
277
+ catchhtouchmove && catchhtouchmove(e);
278
+ }
279
+ else if (touchEvent === 'vtouchmove') {
280
+ catchvtouchmove && catchvtouchmove(e);
281
+ }
282
+ catchtouchmove && catchtouchmove(e);
283
+ }
284
+ };
285
+ const triggerEndOnJS = ({ e }) => {
286
+ extendEvent(e);
287
+ bindtouchend && bindtouchend(e);
288
+ catchtouchend && catchtouchend(e);
289
+ };
247
290
  const gesture = useMemo(() => {
248
- const handleTriggerStart = (e) => {
249
- 'worklet';
250
- extendEvent(e);
251
- bindtouchstart && runOnJS(bindtouchstart)(e);
252
- catchtouchstart && runOnJS(catchtouchstart)(e);
253
- };
254
291
  const handleTriggerMove = (e) => {
255
292
  'worklet';
256
- extendEvent(e);
257
293
  const hasTouchmove = !!bindhtouchmove || !!bindvtouchmove || !!bindtouchmove;
258
294
  const hasCatchTouchmove = !!catchhtouchmove || !!catchvtouchmove || !!catchtouchmove;
259
- if (hasTouchmove) {
260
- if (touchEvent.value === 'htouchmove') {
261
- bindhtouchmove && runOnJS(bindhtouchmove)(e);
262
- }
263
- else if (touchEvent.value === 'vtouchmove') {
264
- bindvtouchmove && runOnJS(bindvtouchmove)(e);
265
- }
266
- bindtouchmove && runOnJS(bindtouchmove)(e);
267
- }
268
- if (hasCatchTouchmove) {
269
- if (touchEvent.value === 'htouchmove') {
270
- catchhtouchmove && runOnJS(catchhtouchmove)(e);
271
- }
272
- else if (touchEvent.value === 'vtouchmove') {
273
- catchvtouchmove && runOnJS(catchvtouchmove)(e);
274
- }
275
- catchtouchmove && runOnJS(catchtouchmove)(e);
295
+ if (hasTouchmove || hasCatchTouchmove) {
296
+ runOnJS(triggerMoveOnJS)({
297
+ e,
298
+ touchEvent: touchEvent.value,
299
+ hasTouchmove,
300
+ hasCatchTouchmove
301
+ });
276
302
  }
277
303
  };
278
- const handleTriggerEnd = (e) => {
279
- 'worklet';
280
- extendEvent(e);
281
- bindtouchend && runOnJS(bindtouchend)(e);
282
- catchtouchend && runOnJS(catchtouchend)(e);
283
- };
284
304
  const gesturePan = Gesture.Pan()
285
305
  .onTouchesDown((e) => {
286
306
  'worklet';
@@ -290,12 +310,14 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
290
310
  x: changedTouches.x,
291
311
  y: changedTouches.y
292
312
  };
293
- handleTriggerStart(e);
313
+ if (bindtouchstart || catchtouchstart) {
314
+ runOnJS(triggerStartOnJS)({ e });
315
+ }
294
316
  })
295
317
  .onTouchesMove((e) => {
296
318
  'worklet';
297
- isMoving.value = true;
298
319
  const changedTouches = e.changedTouches[0] || { x: 0, y: 0 };
320
+ isMoving.value = true;
299
321
  if (isFirstTouch.value) {
300
322
  touchEvent.value = Math.abs(changedTouches.x - startPosition.value.x) > Math.abs(changedTouches.y - startPosition.value.y) ? 'htouchmove' : 'vtouchmove';
301
323
  isFirstTouch.value = false;
@@ -330,7 +352,9 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
330
352
  'worklet';
331
353
  isFirstTouch.value = true;
332
354
  isMoving.value = false;
333
- handleTriggerEnd(e);
355
+ if (bindtouchend || catchtouchend) {
356
+ runOnJS(triggerEndOnJS)({ e });
357
+ }
334
358
  if (disabled)
335
359
  return;
336
360
  if (!inertia) {
@@ -355,9 +379,9 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
355
379
  })
356
380
  .onFinalize((e) => {
357
381
  'worklet';
382
+ isMoving.value = false;
358
383
  if (!inertia || disabled || !animation)
359
384
  return;
360
- isMoving.value = false;
361
385
  if (direction === 'horizontal' || direction === 'all') {
362
386
  xInertialMotion.value = true;
363
387
  offsetX.value = withDecay({
@@ -396,31 +420,42 @@ const _MovableView = forwardRef((movableViewProps, ref) => {
396
420
  ]
397
421
  };
398
422
  });
399
- const injectCatchEvent = (props) => {
400
- const eventHandlers = {};
401
- const catchEventList = [
402
- { name: 'onTouchStart', value: ['catchtouchstart'] },
403
- { name: 'onTouchMove', value: ['catchtouchmove', 'catchvtouchmove', 'catchhtouchmove'] },
404
- { name: 'onTouchEnd', value: ['catchtouchend'] }
423
+ const rewriteCatchEvent = () => {
424
+ const handlers = {};
425
+ const events = [
426
+ { type: 'touchstart' },
427
+ { type: 'touchmove', alias: ['vtouchmove', 'htouchmove'] },
428
+ { type: 'touchend' }
405
429
  ];
406
- catchEventList.forEach(event => {
407
- event.value.forEach(name => {
408
- if (props[name] && !eventHandlers[event.name]) {
409
- eventHandlers[event.name] = (e) => {
410
- e.stopPropagation();
411
- };
412
- }
413
- });
430
+ events.forEach(({ type, alias = [] }) => {
431
+ const hasCatchEvent = props[`catch${type}`] ||
432
+ alias.some(name => props[`catch${name}`]);
433
+ if (hasCatchEvent)
434
+ handlers[`catch${type}`] = noop;
414
435
  });
415
- return eventHandlers;
436
+ return handlers;
416
437
  };
417
- const catchEventHandlers = injectCatchEvent(props);
418
438
  const layoutStyle = !hasLayoutRef.current && hasSelfPercent ? HIDDEN_STYLE : {};
419
- return createElement(GestureDetector, { gesture: gesture }, createElement(Animated.View, extendObject({
439
+ // bind 相关 touch 事件直接由 gesture 触发,无须重复挂载
440
+ // catch 相关 touch 事件需要重写并通过 useInnerProps 注入阻止冒泡逻辑
441
+ const filterProps = omit(props, [
442
+ 'bindtouchstart',
443
+ 'bindtouchmove',
444
+ 'bindvtouchmove',
445
+ 'bindhtouchmove',
446
+ 'bindtouchend',
447
+ 'catchtouchstart',
448
+ 'catchtouchmove',
449
+ 'catchvtouchmove',
450
+ 'catchhtouchmove',
451
+ 'catchtouchend'
452
+ ]);
453
+ const innerProps = useInnerProps(filterProps, extendObject({
420
454
  ref: nodeRef,
421
455
  onLayout: onLayout,
422
456
  style: [innerStyle, animatedStyles, layoutStyle]
423
- }, catchEventHandlers), wrapChildren(props, {
457
+ }, rewriteCatchEvent()));
458
+ return createElement(GestureDetector, { gesture: gesture }, createElement(Animated.View, innerProps, wrapChildren(props, {
424
459
  hasVarDec,
425
460
  varContext: varContextRef.current,
426
461
  textStyle,
@@ -3,7 +3,7 @@ import useInnerProps from './getInnerListeners';
3
3
  import { redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy';
4
4
  import MpxView from './mpx-view';
5
5
  const _Navigator = forwardRef((props, ref) => {
6
- const { children, 'open-type': openType, url, delta } = props;
6
+ const { children, 'open-type': openType, url = '', delta } = props;
7
7
  const handleClick = useCallback(() => {
8
8
  switch (openType) {
9
9
  case 'navigateBack':
@@ -0,0 +1,35 @@
1
+ import React, { useEffect } from 'react';
2
+ import Reanimated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
3
+ import { extendObject } from './utils';
4
+ import { createFaces } from './pickerFaces';
5
+ import { usePickerViewColumnAnimationContext, usePickerViewStyleContext } from './pickerVIewContext';
6
+ const PickerViewColumnItem = ({ item, index, itemHeight, itemWidth = '100%', textStyle, textProps, visibleCount, onItemLayout }) => {
7
+ const textStyleFromAncestor = usePickerViewStyleContext();
8
+ const offsetYShared = usePickerViewColumnAnimationContext();
9
+ const facesShared = useSharedValue(createFaces(itemHeight, visibleCount));
10
+ useEffect(() => {
11
+ facesShared.value = createFaces(itemHeight, visibleCount);
12
+ }, [itemHeight]);
13
+ const animatedStyles = useAnimatedStyle(() => {
14
+ const inputRange = facesShared.value.map((f) => itemHeight * (index + f.index));
15
+ return {
16
+ opacity: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.opacity), Extrapolation.CLAMP),
17
+ transform: [
18
+ { translateY: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.offsetY), Extrapolation.EXTEND) },
19
+ { rotateX: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.deg), Extrapolation.CLAMP) + 'deg' },
20
+ { scale: interpolate(offsetYShared.value, inputRange, facesShared.value.map((x) => x.scale), Extrapolation.EXTEND) }
21
+ ]
22
+ };
23
+ });
24
+ const strKey = `picker-column-item-${index}`;
25
+ const restProps = index === 0 ? { onLayout: onItemLayout } : {};
26
+ const itemProps = extendObject({
27
+ style: extendObject({ height: itemHeight, width: '100%' }, textStyleFromAncestor, textStyle, item.props.style)
28
+ }, textProps, restProps);
29
+ const realItem = React.cloneElement(item, itemProps);
30
+ return (<Reanimated.View key={strKey} style={[{ height: itemHeight, width: itemWidth }, animatedStyles]}>
31
+ {realItem}
32
+ </Reanimated.View>);
33
+ };
34
+ PickerViewColumnItem.displayName = 'MpxPickerViewColumnItem';
35
+ export default PickerViewColumnItem;
@@ -1,42 +1,68 @@
1
- import { Animated, SafeAreaView } from 'react-native';
2
- import React, { forwardRef, useRef, useState, useMemo, useCallback, useEffect } from 'react';
3
- import { useTransformStyle, splitStyle, splitProps, wrapChildren, useLayout, usePrevious } from './utils';
1
+ import React, { forwardRef, useRef, useState, useMemo, useEffect, useCallback } from 'react';
2
+ import { StyleSheet, View } from 'react-native';
3
+ import Reanimated, { useAnimatedRef, useScrollViewOffset } from 'react-native-reanimated';
4
+ import { useTransformStyle, splitStyle, splitProps, useLayout, usePrevious, isAndroid, isIOS } from './utils';
4
5
  import useNodesRef from './useNodesRef';
5
- import { createFaces } from './pickerFaces';
6
- import PickerOverlay from './pickerOverlay';
7
- // 默认的单个选项高度
8
- const DefaultPickerItemH = 36;
9
- // 默认一屏可见选项个数
6
+ import PickerIndicator from './pickerViewIndicator';
7
+ import PickerMask from './pickerViewMask';
8
+ import MpxPickerVIewColumnItem from './mpx-picker-view-column-item';
9
+ import { PickerViewColumnAnimationContext } from './pickerVIewContext';
10
10
  const visibleCount = 5;
11
11
  const _PickerViewColumn = forwardRef((props, ref) => {
12
- const { columnData, columnIndex, initialIndex, onSelectChange, onColumnItemRawHChange, getInnerLayout, style, wrapperStyle, pickerOverlayStyle, 'enable-var': enableVar, 'external-var-context': externalVarContext } = props;
13
- const { normalStyle, hasVarDec, varContextRef, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext });
14
- const { textStyle } = splitStyle(normalStyle);
15
- const { textProps } = splitProps(props);
16
- const scrollViewRef = useRef(null);
12
+ const { columnData, columnIndex, initialIndex, onSelectChange, style, wrapperStyle, pickerMaskStyle, pickerIndicatorStyle, 'enable-var': enableVar, 'external-var-context': externalVarContext } = props;
13
+ const { normalStyle, hasSelfPercent, setWidth, setHeight } = useTransformStyle(style, { enableVar, externalVarContext });
14
+ const { textStyle = {} } = splitStyle(normalStyle);
15
+ const { textProps = {} } = splitProps(props);
16
+ const scrollViewRef = useAnimatedRef();
17
+ const offsetYShared = useScrollViewOffset(scrollViewRef);
17
18
  useNodesRef(props, ref, scrollViewRef, {
18
19
  style: normalStyle
19
20
  });
20
- const { height: pickerH, itemHeight = DefaultPickerItemH } = wrapperStyle;
21
- const [itemRawH, setItemRawH] = useState(0); // 单个选项真实渲染高度
21
+ const { height: pickerH, itemHeight } = wrapperStyle;
22
+ const [itemRawH, setItemRawH] = useState(itemHeight);
22
23
  const maxIndex = useMemo(() => columnData.length - 1, [columnData]);
24
+ const prevScrollingInfo = useRef({ index: initialIndex, y: 0 });
23
25
  const touching = useRef(false);
24
26
  const scrolling = useRef(false);
27
+ const timerResetPosition = useRef(null);
28
+ const timerScrollTo = useRef(null);
25
29
  const activeIndex = useRef(initialIndex);
26
30
  const prevIndex = usePrevious(initialIndex);
27
31
  const prevMaxIndex = usePrevious(maxIndex);
28
- const initialOffset = useMemo(() => ({
29
- x: 0,
30
- y: itemRawH * initialIndex
31
- }), [itemRawH]);
32
- const snapToOffsets = useMemo(() => columnData.map((_, i) => i * itemRawH), [columnData, itemRawH]);
32
+ const { layoutProps } = useLayout({
33
+ props,
34
+ hasSelfPercent,
35
+ setWidth,
36
+ setHeight,
37
+ nodeRef: scrollViewRef
38
+ });
39
+ const paddingHeight = useMemo(() => Math.round((pickerH - itemHeight) / 2), [pickerH, itemHeight]);
40
+ const snapToOffsets = useMemo(() => Array.from({ length: maxIndex + 1 }, (_, i) => i * itemRawH), [maxIndex, itemRawH]);
33
41
  const contentContainerStyle = useMemo(() => {
34
- return [
35
- {
36
- paddingVertical: Math.round(pickerH - itemRawH) / 2
37
- }
38
- ];
39
- }, [pickerH, itemRawH]);
42
+ return [{ paddingVertical: paddingHeight }];
43
+ }, [paddingHeight]);
44
+ const getIndex = useCallback((y) => {
45
+ const calc = Math.round(y / itemRawH);
46
+ return Math.max(0, Math.min(calc, maxIndex));
47
+ }, [itemRawH, maxIndex]);
48
+ const clearTimerResetPosition = useCallback(() => {
49
+ if (timerResetPosition.current) {
50
+ clearTimeout(timerResetPosition.current);
51
+ timerResetPosition.current = null;
52
+ }
53
+ }, []);
54
+ const clearTimerScrollTo = useCallback(() => {
55
+ if (timerScrollTo.current) {
56
+ clearTimeout(timerScrollTo.current);
57
+ timerScrollTo.current = null;
58
+ }
59
+ }, []);
60
+ useEffect(() => {
61
+ return () => {
62
+ clearTimerResetPosition();
63
+ clearTimerScrollTo();
64
+ };
65
+ }, []);
40
66
  useEffect(() => {
41
67
  if (!scrollViewRef.current ||
42
68
  !itemRawH ||
@@ -48,122 +74,120 @@ const _PickerViewColumn = forwardRef((props, ref) => {
48
74
  maxIndex !== prevMaxIndex) {
49
75
  return;
50
76
  }
77
+ clearTimerScrollTo();
78
+ timerScrollTo.current = setTimeout(() => {
79
+ scrollViewRef.current?.scrollTo({
80
+ x: 0,
81
+ y: initialIndex * itemRawH,
82
+ animated: false
83
+ });
84
+ }, isAndroid ? 200 : 0);
51
85
  activeIndex.current = initialIndex;
52
- scrollViewRef.current.scrollTo({
53
- x: 0,
54
- y: itemRawH * initialIndex,
55
- animated: false
56
- });
86
+ }, [itemRawH, maxIndex, initialIndex]);
87
+ const onContentSizeChange = useCallback((_w, h) => {
88
+ const y = initialIndex * itemRawH;
89
+ if (y <= h) {
90
+ clearTimerScrollTo();
91
+ timerScrollTo.current = setTimeout(() => {
92
+ scrollViewRef.current?.scrollTo({ x: 0, y, animated: false });
93
+ }, 0);
94
+ }
57
95
  }, [itemRawH, initialIndex]);
58
- const onScrollViewLayout = () => {
59
- getInnerLayout && getInnerLayout(layoutRef);
60
- };
61
- const { layoutRef, layoutProps } = useLayout({
62
- props,
63
- hasSelfPercent,
64
- setWidth,
65
- setHeight,
66
- nodeRef: scrollViewRef,
67
- onLayout: onScrollViewLayout
68
- });
69
- const onContentSizeChange = (w, h) => {
70
- scrollViewRef.current?.scrollTo({
71
- x: 0,
72
- y: itemRawH * initialIndex,
73
- animated: false
74
- });
75
- };
76
- const onItemLayout = (e) => {
96
+ const onItemLayout = useCallback((e) => {
77
97
  const { height: rawH } = e.nativeEvent.layout;
78
- if (rawH && itemRawH !== rawH) {
79
- setItemRawH(rawH);
80
- onColumnItemRawHChange(rawH);
98
+ const roundedH = Math.round(rawH);
99
+ if (roundedH && roundedH !== itemRawH) {
100
+ setItemRawH(roundedH);
81
101
  }
82
- };
83
- const onTouchStart = () => {
84
- touching.current = true;
85
- };
86
- const onTouchEnd = () => {
87
- touching.current = false;
88
- };
89
- const onTouchCancel = () => {
90
- touching.current = false;
91
- };
92
- const onMomentumScrollBegin = () => {
93
- scrolling.current = true;
94
- };
95
- const onMomentumScrollEnd = (e) => {
96
- scrolling.current = false;
97
- if (!itemRawH) {
102
+ }, [itemRawH]);
103
+ const resetScrollPosition = useCallback((y) => {
104
+ if (touching.current || scrolling.current) {
98
105
  return;
99
106
  }
107
+ scrolling.current = true;
108
+ const targetIndex = getIndex(y);
109
+ scrollViewRef.current?.scrollTo({ x: 0, y: targetIndex * itemRawH, animated: false });
110
+ }, [itemRawH, getIndex]);
111
+ const onMomentumScrollBegin = useCallback(() => {
112
+ isIOS && clearTimerResetPosition();
113
+ scrolling.current = true;
114
+ }, []);
115
+ const onMomentumScrollEnd = useCallback((e) => {
116
+ scrolling.current = false;
100
117
  const { y: scrollY } = e.nativeEvent.contentOffset;
101
- let calcIndex = Math.round(scrollY / itemRawH);
102
- activeIndex.current = calcIndex;
103
- if (calcIndex !== initialIndex) {
104
- calcIndex = Math.max(0, Math.min(calcIndex, maxIndex)) || 0;
118
+ if (isIOS && scrollY % itemRawH !== 0) {
119
+ return resetScrollPosition(scrollY);
120
+ }
121
+ const calcIndex = getIndex(scrollY);
122
+ if (calcIndex !== activeIndex.current) {
123
+ activeIndex.current = calcIndex;
105
124
  onSelectChange(calcIndex);
106
125
  }
107
- };
108
- const offsetY = useRef(new Animated.Value(0)).current;
109
- const onScroll = useMemo(() => Animated.event([{ nativeEvent: { contentOffset: { y: offsetY } } }], {
110
- useNativeDriver: true
111
- }), [offsetY]);
112
- const faces = useMemo(() => createFaces(itemRawH, visibleCount), [itemRawH]);
113
- const getTransform = useCallback((index) => {
114
- const inputRange = faces.map((f) => itemRawH * (index + f.index));
115
- return {
116
- opacity: offsetY.interpolate({
117
- inputRange: inputRange,
118
- outputRange: faces.map((x) => x.opacity),
119
- extrapolate: 'clamp'
120
- }),
121
- rotateX: offsetY.interpolate({
122
- inputRange: inputRange,
123
- outputRange: faces.map((x) => `${x.deg}deg`),
124
- extrapolate: 'extend'
125
- }),
126
- translateY: offsetY.interpolate({
127
- inputRange: inputRange,
128
- outputRange: faces.map((x) => x.offsetY),
129
- extrapolate: 'extend'
130
- })
126
+ }, [itemRawH, getIndex, onSelectChange, resetScrollPosition]);
127
+ const onScrollBeginDrag = useCallback(() => {
128
+ isIOS && clearTimerResetPosition();
129
+ touching.current = true;
130
+ prevScrollingInfo.current = {
131
+ index: activeIndex.current,
132
+ y: activeIndex.current * itemRawH
131
133
  };
132
- }, [offsetY, faces, itemRawH]);
133
- const renderInnerchild = () => columnData.map((item, index) => {
134
- const InnerProps = index === 0 ? { onLayout: onItemLayout } : {};
135
- const strKey = `picker-column-${columnIndex}-${index}`;
136
- const { opacity, rotateX, translateY } = getTransform(index);
137
- return (<Animated.View key={strKey} {...InnerProps} style={[
138
- {
139
- height: itemHeight || DefaultPickerItemH,
140
- width: '100%',
141
- opacity,
142
- transform: [
143
- { translateY },
144
- { rotateX },
145
- { perspective: 1000 } // 适配 Android
146
- ]
134
+ }, [itemRawH]);
135
+ const onScrollEndDrag = useCallback((e) => {
136
+ touching.current = false;
137
+ if (isIOS) {
138
+ const { y } = e.nativeEvent.contentOffset;
139
+ if (y % itemRawH === 0) {
140
+ onMomentumScrollEnd({ nativeEvent: { contentOffset: { y } } });
141
+ }
142
+ else if (y > 0 && y < snapToOffsets[maxIndex]) {
143
+ timerResetPosition.current = setTimeout(() => {
144
+ resetScrollPosition(y);
145
+ }, 10);
146
+ }
147
+ }
148
+ }, [itemRawH, maxIndex, snapToOffsets, onMomentumScrollEnd, resetScrollPosition]);
149
+ const onScroll = useCallback((e) => {
150
+ // 全局注册的振动触感 hook
151
+ const pickerVibrate = global.__mpx?.config?.rnConfig?.pickerVibrate;
152
+ if (typeof pickerVibrate !== 'function') {
153
+ return;
154
+ }
155
+ const { y } = e.nativeEvent.contentOffset;
156
+ const { index: prevIndex, y: _y } = prevScrollingInfo.current;
157
+ if (touching.current || scrolling.current) {
158
+ if (Math.abs(y - _y) >= itemRawH) {
159
+ const currentId = getIndex(y);
160
+ if (currentId !== prevIndex) {
161
+ prevScrollingInfo.current = {
162
+ index: currentId,
163
+ y: currentId * itemRawH
164
+ };
165
+ // vibrateShort({ type: 'selection' })
166
+ pickerVibrate();
147
167
  }
148
- ]}>
149
- {wrapChildren({ children: item }, {
150
- hasVarDec,
151
- varContext: varContextRef.current,
152
- textStyle,
153
- textProps
154
- })}
155
- </Animated.View>);
168
+ }
169
+ }
170
+ }, [itemRawH, getIndex]);
171
+ const renderInnerchild = () => columnData.map((item, index) => {
172
+ return (<MpxPickerVIewColumnItem key={index} item={item} index={index} itemHeight={itemHeight} textStyle={textStyle} textProps={textProps} visibleCount={visibleCount} onItemLayout={onItemLayout}/>);
156
173
  });
157
174
  const renderScollView = () => {
158
- return (<Animated.ScrollView ref={scrollViewRef} bounces={true} horizontal={false} pagingEnabled={false} nestedScrollEnabled={true} removeClippedSubviews={true} showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} {...layoutProps} scrollEventThrottle={16} contentContainerStyle={contentContainerStyle} contentOffset={initialOffset} snapToOffsets={snapToOffsets} onContentSizeChange={onContentSizeChange} onScroll={onScroll} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} onTouchCancel={onTouchCancel} onMomentumScrollBegin={onMomentumScrollBegin} onMomentumScrollEnd={onMomentumScrollEnd}>
159
- {renderInnerchild()}
160
- </Animated.ScrollView>);
175
+ return (<PickerViewColumnAnimationContext.Provider value={offsetYShared}>
176
+ <Reanimated.ScrollView ref={scrollViewRef} bounces={true} horizontal={false} nestedScrollEnabled={true} removeClippedSubviews={false} showsVerticalScrollIndicator={false} showsHorizontalScrollIndicator={false} scrollEventThrottle={16} {...layoutProps} style={[{ width: '100%' }]} decelerationRate="fast" snapToOffsets={snapToOffsets} onScroll={onScroll} onScrollBeginDrag={onScrollBeginDrag} onScrollEndDrag={onScrollEndDrag} onMomentumScrollBegin={onMomentumScrollBegin} onMomentumScrollEnd={onMomentumScrollEnd} onContentSizeChange={onContentSizeChange} contentContainerStyle={contentContainerStyle}>
177
+ {renderInnerchild()}
178
+ </Reanimated.ScrollView>
179
+ </PickerViewColumnAnimationContext.Provider>);
161
180
  };
162
- const renderOverlay = () => (<PickerOverlay itemHeight={itemHeight} overlayItemStyle={pickerOverlayStyle}/>);
163
- return (<SafeAreaView style={[{ display: 'flex', flex: 1 }]}>
181
+ const renderIndicator = () => (<PickerIndicator itemHeight={itemHeight} indicatorItemStyle={pickerIndicatorStyle}/>);
182
+ const renderMask = () => (<PickerMask itemHeight={itemHeight} maskContainerStyle={pickerMaskStyle}/>);
183
+ return (<View style={[styles.wrapper, normalStyle]}>
164
184
  {renderScollView()}
165
- {renderOverlay()}
166
- </SafeAreaView>);
185
+ {renderMask()}
186
+ {renderIndicator()}
187
+ </View>);
188
+ });
189
+ const styles = StyleSheet.create({
190
+ wrapper: { display: 'flex', flex: 1 }
167
191
  });
168
192
  _PickerViewColumn.displayName = 'MpxPickerViewColumn';
169
193
  export default _PickerViewColumn;