@mpxjs/webpack-plugin 2.10.17-unocss.1 → 2.10.18-beta.1

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 (40) hide show
  1. package/lib/index.js +49 -36
  2. package/lib/platform/style/wx/index.js +0 -2
  3. package/lib/platform/template/wx/component-config/camera.js +12 -0
  4. package/lib/platform/template/wx/component-config/unsupported.js +1 -1
  5. package/lib/react/processStyles.js +8 -39
  6. package/lib/react/style-helper.js +5 -16
  7. package/lib/resolver/ExtendComponentsPlugin.js +60 -0
  8. package/lib/runtime/components/ali/mpx-section-list.mpx +566 -0
  9. package/lib/runtime/components/ali/mpx-sticky-header.mpx +212 -0
  10. package/lib/runtime/components/ali/mpx-sticky-section.mpx +17 -0
  11. package/lib/runtime/components/react/dist/animationHooks/useAnimationAPIHooks.js +0 -1
  12. package/lib/runtime/components/react/dist/mpx-async-suspense.jsx +2 -1
  13. package/lib/runtime/components/react/dist/mpx-input.jsx +10 -3
  14. package/lib/runtime/components/react/dist/mpx-keyboard-avoiding-view.jsx +13 -2
  15. package/lib/runtime/components/react/dist/mpx-swiper.jsx +60 -40
  16. package/lib/runtime/components/react/dist/utils.jsx +8 -4
  17. package/lib/runtime/components/react/mpx-async-suspense.tsx +2 -1
  18. package/lib/runtime/components/react/mpx-camera.tsx +327 -0
  19. package/lib/runtime/components/react/mpx-input.tsx +11 -2
  20. package/lib/runtime/components/react/mpx-keyboard-avoiding-view.tsx +13 -2
  21. package/lib/runtime/components/react/mpx-section-list.tsx +439 -0
  22. package/lib/runtime/components/react/mpx-swiper.tsx +113 -66
  23. package/lib/runtime/components/react/mpx-web-view.tsx +1 -1
  24. package/lib/runtime/components/react/tsconfig.json +1 -1
  25. package/lib/runtime/components/react/utils.tsx +8 -4
  26. package/lib/runtime/components/web/mpx-scroll-view.vue +5 -2
  27. package/lib/runtime/components/web/mpx-section-list.vue +551 -0
  28. package/lib/runtime/components/wx/mpx-section-list-default/list-footer.mpx +26 -0
  29. package/lib/runtime/components/wx/mpx-section-list-default/list-header.mpx +26 -0
  30. package/lib/runtime/components/wx/mpx-section-list-default/list-item.mpx +26 -0
  31. package/lib/runtime/components/wx/mpx-section-list-default/section-header.mpx +26 -0
  32. package/lib/runtime/components/wx/mpx-section-list.mpx +209 -0
  33. package/lib/runtime/components/wx/mpx-sticky-header.mpx +40 -0
  34. package/lib/runtime/components/wx/mpx-sticky-section.mpx +31 -0
  35. package/lib/template-compiler/compiler.js +7 -3
  36. package/lib/utils/const.js +29 -0
  37. package/lib/utils/dom-tag-config.js +1 -1
  38. package/lib/wxss/loader.js +4 -1
  39. package/lib/wxss/utils.js +7 -2
  40. package/package.json +3 -3
@@ -0,0 +1,212 @@
1
+ <template>
2
+ <view class="mpx-sticky-header-container">
3
+ <view class="mpx-sticky-header" wx:ref="stickyHeader" wx:style="{{stickyHeaderStyle}}">
4
+ <slot></slot>
5
+ </view>
6
+ <view
7
+ class="mpx-sticky-header-placeholder"
8
+ id="{{stickyId}}"
9
+ wx:style="{{placeholderStyle}}"
10
+ ></view>
11
+ </view>
12
+ </template>
13
+
14
+ <script>
15
+ import mpx, { createComponent } from '@mpxjs/core'
16
+
17
+ createComponent({
18
+ properties: {
19
+ offsetTop: {
20
+ type: Number,
21
+ value: 0
22
+ },
23
+ scrollViewId: {
24
+ type: String,
25
+ value: ''
26
+ },
27
+ stickyId: {
28
+ type: String,
29
+ value: ''
30
+ },
31
+ padding: Array,
32
+ enablePolling: {
33
+ type: Boolean,
34
+ value: false
35
+ },
36
+ pollingDuration: {
37
+ type: Number,
38
+ value: 300
39
+ }
40
+ },
41
+ data: {
42
+ isStickOnTop: false,
43
+ scrollOffsetTop: 0,
44
+ headerHeight: 0,
45
+ stickyHeader: '',
46
+ headerTop: 0,
47
+ lastIntersectionRatio: -1,
48
+ pollingTimer: null
49
+ },
50
+ computed: {
51
+ paddingStyle() {
52
+ if (!this.padding || !Array.isArray(this.padding)) {
53
+ return ''
54
+ }
55
+ const [top = 0, right = 0, bottom = 0, left = 0] = this.padding
56
+ return `padding: ${top}px ${right}px ${bottom}px ${left}px;`
57
+ },
58
+ stickyHeaderStyle() {
59
+ const baseStyle = this.isStickOnTop
60
+ ? `position: fixed;top: ${this.scrollOffsetTop + this.offsetTop}px;`
61
+ : ''
62
+ return baseStyle + this.paddingStyle
63
+ },
64
+ placeholderStyle() {
65
+ const position = this.isStickOnTop ? 'relative' : 'absolute'
66
+ return `position: ${position};height: ${this.headerHeight}px;`
67
+ }
68
+ },
69
+ ready() {
70
+ if (!this.scrollViewId) {
71
+ console.error('[mpx runtime error]: scroll-view-id is necessary property in ali environment')
72
+ }
73
+ if (!this.stickyId) {
74
+ console.error('[mpx runtime error]: sticky-id is necessary property in ali environment')
75
+ }
76
+ this.initStickyHeader()
77
+ // 启动轮询
78
+ if (this.enablePolling) {
79
+ this.startPolling()
80
+ }
81
+ },
82
+ methods: {
83
+ initStickyHeader() {
84
+ this.createSelectorQuery()
85
+ .select('.mpx-sticky-header')
86
+ .boundingClientRect()
87
+ .exec((rect = []) => {
88
+ this.headerHeight = rect[0]?.height || 0
89
+ })
90
+
91
+ mpx
92
+ .createSelectorQuery()
93
+ .select(`#${this.scrollViewId}`)
94
+ .boundingClientRect()
95
+ .exec((res) => {
96
+ if (!res) return
97
+ this.scrollOffsetTop = res[0]?.top || 0
98
+ this.stickyHeader = mpx.createIntersectionObserver({
99
+ thresholds: [0.1, 0.9]
100
+ })
101
+ this.initObserver()
102
+ })
103
+ },
104
+ initObserver() {
105
+ this.stickyHeader.relativeTo(`#${this.scrollViewId}`).observe(`#${this.stickyId}`, (res) => {
106
+ const { intersectionRatio, intersectionRect, relativeRect, boundingClientRect } = res
107
+ if (intersectionRatio < this.lastIntersectionRatio) {
108
+ // boundingClientRect.top < relativeRect.top fixed,否则默认布局
109
+ this.handleStickyStateChange(boundingClientRect.top < relativeRect.top)
110
+ } else if (intersectionRatio > this.lastIntersectionRatio) {
111
+ this.handleStickyStateChange(false)
112
+ }
113
+ this.lastIntersectionRatio = intersectionRatio
114
+ })
115
+ },
116
+ refresh() {
117
+ // 并行执行两个独立的查询
118
+ Promise.all([
119
+ // 查询1:组件内部元素
120
+ new Promise((resolve) => {
121
+ this.createSelectorQuery()
122
+ .select('.mpx-sticky-header')
123
+ .boundingClientRect()
124
+ .select('.mpx-sticky-header-placeholder')
125
+ .boundingClientRect()
126
+ .exec((results) => {
127
+ resolve(results)
128
+ })
129
+ }),
130
+ // 查询2:页面级scrollView
131
+ new Promise((resolve) => {
132
+ mpx.createSelectorQuery()
133
+ .select(`#${this.scrollViewId}`)
134
+ .boundingClientRect()
135
+ .select(`#${this.scrollViewId}`)
136
+ .scrollOffset()
137
+ .exec((results) => {
138
+ resolve(results)
139
+ })
140
+ })
141
+ ]).then(([componentResults, scrollResults]) => {
142
+ const [stickyHeaderRect, placeholderRect] = componentResults
143
+ const [scrollViewRect, scrollOffsetData] = scrollResults
144
+
145
+ if (!stickyHeaderRect || !placeholderRect || !scrollViewRect || !scrollOffsetData) return
146
+
147
+ this.scrollOffsetTop = scrollViewRect.top || 0
148
+ this.headerHeight = stickyHeaderRect.height
149
+
150
+ // 模拟 offsetTop 计算:sticky placeholder具体顶部距离 - scrollView顶部 + scrollView滚动位置
151
+ // 必须用 placeholder 到顶部距离,用 sticky 如果刚好差值为 0, 区分不出是本身就在顶部还是 fixed 在顶部
152
+ this.headerTop = placeholderRect.top - this.scrollOffsetTop + scrollOffsetData.scrollTop
153
+
154
+ this.handleStickyStateChange(scrollOffsetData.scrollTop > this.headerTop)
155
+ })
156
+ },
157
+ handleStickyStateChange(shouldStick) {
158
+ // 如果状态没有变化,直接返回
159
+ if (shouldStick === this.isStickOnTop) {
160
+ return
161
+ }
162
+ // 更新状态
163
+ this.isStickOnTop = shouldStick
164
+ // 触发事件
165
+ this.triggerEvent('stickontopchange', {
166
+ isStickOnTop: shouldStick,
167
+ id: this.stickyId
168
+ })
169
+ },
170
+ // 启动轮询
171
+ startPolling() {
172
+ if (this.pollingTimer) {
173
+ clearInterval(this.pollingTimer)
174
+ }
175
+ this.pollingTimer = setInterval(() => {
176
+ this.refresh()
177
+ }, this.pollingDuration)
178
+ },
179
+ // 停止轮询
180
+ stopPolling() {
181
+ if (this.pollingTimer) {
182
+ clearInterval(this.pollingTimer)
183
+ this.pollingTimer = null
184
+ }
185
+ }
186
+ },
187
+ detached() {
188
+ // 清理观察器
189
+ if (this.stickyHeader) {
190
+ this.stickyHeader.disconnect()
191
+ }
192
+ // 清理轮询定时器
193
+ if (this.pollingTimer) {
194
+ clearInterval(this.pollingTimer)
195
+ this.pollingTimer = null
196
+ }
197
+ }
198
+ })
199
+ </script>
200
+ <style lang="stylus" scoped>
201
+ .mpx-sticky-header-container
202
+ position relative
203
+ .mpx-sticky-header-placeholder
204
+ position absolute
205
+ left 0
206
+ right 0
207
+ top 0
208
+ pointer-events none
209
+ .mpx-sticky-header
210
+ width 100%
211
+ box-sizing border-box
212
+ </style>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <view class="mpx-sticky-section">
3
+ <slot></slot>
4
+ </view>
5
+ </template>
6
+
7
+ <script>
8
+ import { createComponent } from '@mpxjs/core'
9
+
10
+ createComponent({
11
+
12
+ })
13
+ </script>
14
+ <style lang="stylus" scoped>
15
+ .mpx-sticky-section
16
+ position relative
17
+ </style>
@@ -101,7 +101,6 @@ export default function useAnimationAPIHooks(props) {
101
101
  // 动画起始值和终态值类型不一致报错提示一下
102
102
  error(`[Mpx runtime error]: Value types of property ${key} must be consistent during the animation`);
103
103
  }
104
- // Todo 对齐wx
105
104
  const animation = (toVal === 'auto' && !isNaN(+shareVal)) || (shareVal === 'auto' && !isNaN(+toVal)) ? toVal : getAnimation({ key, value: toVal }, { delay, duration, easing }, needSetCallback ? callback : undefined);
106
105
  needSetCallback = false;
107
106
  if (!sequence[key]) {
@@ -63,7 +63,8 @@ const DefaultFallback = ({ onReload }) => {
63
63
  };
64
64
  const DefaultLoading = () => {
65
65
  // eslint-disable-next-line @typescript-eslint/no-var-requires
66
- const FastImage = require('@d11/react-native-fast-image');
66
+ const FastImageModule = require('@d11/react-native-fast-image');
67
+ const FastImage = FastImageModule.default || FastImageModule;
67
68
  return (<View style={styles.container}>
68
69
  <FastImage style={styles.loadingImage} source={{
69
70
  uri: 'https://dpubstatic.udache.com/static/dpubimg/439jiCVOtNOnEv9F2LaDs_loading.gif'
@@ -40,7 +40,7 @@
40
40
  import { forwardRef, useRef, useState, useContext, useEffect, createElement } from 'react';
41
41
  import { TextInput } from 'react-native';
42
42
  import { warn } from '@mpxjs/utils';
43
- import { useUpdateEffect, useTransformStyle, useLayout, extendObject } from './utils';
43
+ import { useUpdateEffect, useTransformStyle, useLayout, extendObject, isAndroid } from './utils';
44
44
  import useInnerProps, { getCustomEvent } from './getInnerListeners';
45
45
  import useNodesRef from './useNodesRef';
46
46
  import { FormContext, KeyboardAvoidContext } from './context';
@@ -73,6 +73,8 @@ const Input = forwardRef((props, ref) => {
73
73
  return '';
74
74
  };
75
75
  const defaultValue = parseValue(value);
76
+ // 微信小程序的 input 永远是单行,textAlignVertical 固定为 auto
77
+ // multiline 为 true 时表示是 textarea 组件复用此逻辑
76
78
  const textAlignVertical = multiline ? 'top' : 'auto';
77
79
  const isAutoFocus = !!autoFocus || !!focus;
78
80
  const tmpValue = useRef(defaultValue);
@@ -280,6 +282,11 @@ const Input = forwardRef((props, ref) => {
280
282
  ? nodeRef.current?.focus()
281
283
  : nodeRef.current?.blur();
282
284
  }, [isAutoFocus]);
285
+ // 使用 multiline 来修复光标位置问题
286
+ // React Native 的 TextInput 在 textAlign center + placeholder 时光标会跑到右边
287
+ // 这个问题只在 Android 上出现
288
+ // 参考:https://github.com/facebook/react-native/issues/28794 (Android only)
289
+ const needMultilineFix = isAndroid && !multiline;
283
290
  const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
284
291
  ref: nodeRef,
285
292
  style: extendObject({}, normalStyle, layoutStyle),
@@ -298,7 +305,7 @@ const Input = forwardRef((props, ref) => {
298
305
  underlineColorAndroid: 'rgba(0,0,0,0)',
299
306
  textAlignVertical: textAlignVertical,
300
307
  placeholderTextColor: placeholderStyle?.color,
301
- multiline: !!multiline,
308
+ multiline: multiline || needMultilineFix,
302
309
  onTouchStart,
303
310
  onTouchEnd,
304
311
  onFocus,
@@ -307,7 +314,7 @@ const Input = forwardRef((props, ref) => {
307
314
  onSelectionChange,
308
315
  onContentSizeChange,
309
316
  onSubmitEditing: bindconfirm && onSubmitEditing
310
- }, !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }), [
317
+ }, needMultilineFix ? { numberOfLines: 1 } : {}, !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }), [
311
318
  'type',
312
319
  'password',
313
320
  'placeholder-style',
@@ -14,6 +14,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
14
14
  // 比如机型 iPhone 11 Pro,可能会导致显隐动画冲突
15
15
  // 因此增加状态标记 + cancelAnimation 来优化
16
16
  const isShow = useRef(false);
17
+ const keybaordHandleTimerRef = useRef(null);
17
18
  const animatedStyle = useAnimatedStyle(() => ({
18
19
  // translate/position top+ overflow hidden 在 android 上时因为键盘顶起让页面高度变小,同时元素位置上移
19
20
  // 此时最底部的区域是超出了页面高度的,hidden生效就被隐藏掉,因此需要 android 配置聚焦时禁用高度缩小
@@ -62,7 +63,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
62
63
  // 重置标记位
63
64
  keyboardAvoid.current.readyToShow = false;
64
65
  }
65
- if (!keyboardAvoid?.current || isShow.current) {
66
+ if (!keyboardAvoid?.current) {
66
67
  return;
67
68
  }
68
69
  isShow.current = true;
@@ -102,13 +103,23 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }) => {
102
103
  }
103
104
  }
104
105
  if (isIOS) {
105
- subscriptions = [Keyboard.addListener('keyboardWillShow', keybaordAvoding), Keyboard.addListener('keyboardWillHide', resetKeyboard)];
106
+ subscriptions = [
107
+ Keyboard.addListener('keyboardWillShow', (evt) => {
108
+ if (keybaordHandleTimerRef.current) {
109
+ clearTimeout(keybaordHandleTimerRef.current);
110
+ }
111
+ // iphone 在input聚焦时长按滑动后会导致 show 事件先于 focus 事件发生,因此等一下,等 focus 先触发拿到 input,避免键盘出现但input没顶上去
112
+ keybaordHandleTimerRef.current = setTimeout(() => keybaordAvoding(evt), 32);
113
+ }),
114
+ Keyboard.addListener('keyboardWillHide', resetKeyboard)
115
+ ];
106
116
  }
107
117
  else {
108
118
  subscriptions = [Keyboard.addListener('keyboardDidShow', keybaordAvoding), Keyboard.addListener('keyboardDidHide', resetKeyboard)];
109
119
  }
110
120
  return () => {
111
121
  subscriptions.forEach(subscription => subscription.remove());
122
+ keybaordHandleTimerRef.current && clearTimeout(keybaordHandleTimerRef.current);
112
123
  };
113
124
  }, [keyboardAvoid]);
114
125
  return (<View style={style} onTouchEnd={onTouchEnd} onTouchMove={onTouchEnd}>
@@ -126,9 +126,11 @@ const SwiperWrapper = forwardRef((props, ref) => {
126
126
  const preAbsolutePos = useSharedValue(0);
127
127
  // 记录从onBegin 到 onTouchesUp 时移动的距离
128
128
  const moveTranstion = useSharedValue(0);
129
+ // 记录用户手滑动的方向
130
+ const moveDir = useSharedValue(0);
129
131
  const timerId = useRef(0);
130
132
  const intervalTimer = props.interval || 500;
131
- // 记录是否首次
133
+ // 记录是否首次,首次不能触发bindchange回调
132
134
  const isFirstRef = useRef(true);
133
135
  const simultaneousHandlers = flatGesture(originSimultaneousHandlers);
134
136
  const waitForHandlers = flatGesture(waitFor);
@@ -356,7 +358,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
356
358
  resumeLoop
357
359
  };
358
360
  }, []);
359
- function handleSwiperChange(current, pCurrent) {
361
+ function handleSwiperChange(current) {
360
362
  const eventData = getCustomEvent('change', {}, { detail: { current, source: 'touch' }, layoutRef: layoutRef });
361
363
  bindchange && bindchange(eventData);
362
364
  }
@@ -465,6 +467,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
465
467
  }
466
468
  }, [circular, patchElmNum, displayMultipleItems]);
467
469
  const { gestureHandler } = useMemo(() => {
470
+ // 基于transdir + 当前offset计算索引
468
471
  function getTargetPosition(eventData) {
469
472
  'worklet';
470
473
  // 移动的距离
@@ -578,10 +581,8 @@ const SwiperWrapper = forwardRef((props, ref) => {
578
581
  }
579
582
  });
580
583
  }
581
- // 当前的offset和index多对应的offset进行对比,判断是否超过一半
582
- function computeHalf(eventData) {
584
+ function computeHalf() {
583
585
  'worklet';
584
- const { transdir } = eventData;
585
586
  const currentOffset = Math.abs(offset.value);
586
587
  let preOffset = (currentIndex.value + patchElmNumShared.value) * step.value;
587
588
  if (circularShared.value) {
@@ -590,35 +591,14 @@ const SwiperWrapper = forwardRef((props, ref) => {
590
591
  // 正常事件中拿到的translation值(正向滑动<0,倒着滑>0)
591
592
  const diffOffset = preOffset - currentOffset;
592
593
  const half = Math.abs(diffOffset) > step.value / 2;
593
- const isTriggerUpdateHalf = (transdir < 0 && currentOffset < preOffset) || (transdir > 0 && currentOffset > preOffset);
594
- return {
595
- diffOffset,
596
- half,
597
- isTriggerUpdateHalf
598
- };
599
- }
600
- function handleLongPress(eventData) {
601
- 'worklet';
602
- const { diffOffset, half, isTriggerUpdateHalf } = computeHalf(eventData);
603
- if (+diffOffset === 0) {
604
- runOnJS(runOnJSCallback)('resumeLoop');
605
- }
606
- else if (isTriggerUpdateHalf) {
607
- // 如果触发了onUpdate时的索引变更
608
- handleEnd(eventData);
609
- }
610
- else if (half) {
611
- handleEnd(eventData);
612
- }
613
- else {
614
- handleBack(eventData);
615
- }
594
+ return half;
616
595
  }
617
596
  function reachBoundary(eventData) {
618
597
  'worklet';
619
598
  // 1. 基于当前的offset和translation判断是否超过当前边界值
620
599
  const { translation } = eventData;
621
- const boundaryStart = -patchElmNumShared.value * step.value;
600
+ // 与终点的逻辑对齐,都是超过补位元素对应的起点offset
601
+ const boundaryStart = 0;
622
602
  const boundaryEnd = -(childrenLength.value + patchElmNumShared.value) * step.value;
623
603
  const moveToOffset = offset.value + translation;
624
604
  let isBoundary = false;
@@ -635,7 +615,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
635
615
  // 超过边界的距离
636
616
  const exceedLength = Math.abs(boundaryStart) - Math.abs(moveToOffset);
637
617
  // 计算对标正常元素所在的offset
638
- resetOffset = (patchElmNumShared.value + childrenLength.value - 1) * step.value + (step.value - exceedLength);
618
+ resetOffset = (patchElmNumShared.value + childrenLength.value - 1) * step.value - exceedLength;
639
619
  }
640
620
  return {
641
621
  isBoundary,
@@ -674,6 +654,14 @@ const SwiperWrapper = forwardRef((props, ref) => {
674
654
  }
675
655
  return finalOffset;
676
656
  }
657
+ // 设置手势移动的方向
658
+ function setMoveDir(curAbsoPos) {
659
+ 'worklet';
660
+ const distance = curAbsoPos - preAbsolutePos.value;
661
+ if (distance) {
662
+ moveDir.value = curAbsoPos - preAbsolutePos.value;
663
+ }
664
+ }
677
665
  const gesturePan = Gesture.Pan()
678
666
  .onBegin((e) => {
679
667
  'worklet';
@@ -695,9 +683,9 @@ const SwiperWrapper = forwardRef((props, ref) => {
695
683
  transdir: moveDistance
696
684
  };
697
685
  // 1. 支持滑动中超出一半更新索引的能力:只更新索引并不会影响onFinalize依据当前offset计算的索引
698
- const { half } = computeHalf(eventData);
699
- if (childrenLength.value > 1 && half) {
700
- const { selectedIndex } = getTargetPosition(eventData);
686
+ const offsetHalf = computeHalf();
687
+ if (childrenLength.value > 1 && offsetHalf) {
688
+ const { selectedIndex } = getTargetPosition({ transdir: moveDistance });
701
689
  currentIndex.value = selectedIndex;
702
690
  }
703
691
  // 2. 非循环: 处理用户一直拖拽到临界点的场景,如果放到onFinalize无法阻止offset.value更新为越界的值
@@ -709,6 +697,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
709
697
  const finalOffset = handleResistanceMove(eventData);
710
698
  offset.value = finalOffset;
711
699
  }
700
+ setMoveDir(e[strAbso]);
712
701
  preAbsolutePos.value = e[strAbso];
713
702
  return;
714
703
  }
@@ -716,6 +705,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
716
705
  if (circularShared.value && childrenLength.value === 1) {
717
706
  const finalOffset = handleResistanceMove(eventData);
718
707
  offset.value = finalOffset;
708
+ setMoveDir(e[strAbso]);
719
709
  preAbsolutePos.value = e[strAbso];
720
710
  return;
721
711
  }
@@ -727,6 +717,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
727
717
  else {
728
718
  offset.value = moveDistance + offset.value;
729
719
  }
720
+ setMoveDir(e[strAbso]);
730
721
  preAbsolutePos.value = e[strAbso];
731
722
  })
732
723
  .onFinalize((e) => {
@@ -735,10 +726,21 @@ const SwiperWrapper = forwardRef((props, ref) => {
735
726
  return;
736
727
  touchfinish.value = true;
737
728
  // 触发过onUpdate正常情况下e[strAbso] - preAbsolutePos.value=0; 未触发过onUpdate的情况下e[strAbso] - preAbsolutePos.value 不为0
729
+ // 正常状态下基于onUpdate时的moveDir判断方向、未触发onUpdate的则基于onBegin的moveTranstion判断方向
738
730
  const moveDistance = e[strAbso] - preAbsolutePos.value;
731
+ // 默认兜底方向: 以onBegin为起点,因一些原因未触发onUpdate但是触发了位移
732
+ const defaultDir = e[strAbso] - moveTranstion.value;
733
+ // 实时方向:方向基于onUpdate时的方向,滑动的速度超过阈值时基于实时的滑动方向计算
734
+ const realtimeData = {
735
+ transdir: moveDir.value || defaultDir
736
+ };
737
+ // 起始方向:基于用户起始手势
738
+ const originData = {
739
+ transdir: defaultDir
740
+ };
739
741
  const eventData = {
740
742
  translation: moveDistance,
741
- transdir: moveDistance !== 0 ? moveDistance : e[strAbso] - moveTranstion.value
743
+ transdir: realtimeData.transdir
742
744
  };
743
745
  // 1. 只有一个元素:循环 和 非循环状态,都走回弹效果
744
746
  if (childrenLength.value === 1) {
@@ -752,21 +754,39 @@ const SwiperWrapper = forwardRef((props, ref) => {
752
754
  // 非循环支持最后元素可滑动能力后,向左快速移动未超过最大可移动范围一半,因为offset为正值,向左滑动handleBack,默认向上取整
753
755
  // 但是在offset大于0时,取0。[-100, 0](back取0), [0, 100](back取1), 所以handleLongPress里的处理逻辑需要兼容支持,因此这里直接单独处理,不耦合下方公共的判断逻辑。
754
756
  if (!circularShared.value && !canMove(eventData)) {
755
- if (eventData.transdir < 0) {
756
- handleBack(eventData);
757
+ if (realtimeData.transdir < 0) {
758
+ handleBack(realtimeData);
757
759
  }
758
760
  else {
759
- handleEnd(eventData);
761
+ handleEnd(realtimeData);
760
762
  }
761
763
  return;
762
764
  }
763
765
  // 3. 非循环状态可移动态、循环状态, 正常逻辑处理
764
766
  const velocity = e[strVelocity];
765
- if (Math.abs(velocity) < longPressRatio) {
766
- handleLongPress(eventData);
767
+ // 用于判断是否超过一半,基于索引判断是否超过一半不可行(1.滑动过程中索引会变更导致计算反向, 2.边界场景会更新offset也会导致基于索引+offset判断实效)
768
+ const tmp = offset.value % step.value > step.value / 2;
769
+ // 小于0手向左滑动
770
+ const offsetHalf = originData.transdir < 0 ? tmp : !tmp;
771
+ if (offsetHalf) {
772
+ if (Math.abs(velocity) > longPressRatio) {
773
+ // 超过速度阈值,按照实时方向(快速来回滑动)
774
+ handleEnd(realtimeData);
775
+ }
776
+ else {
777
+ // 超过速度阈值,按照起始方向(慢速长按)
778
+ handleEnd(originData);
779
+ }
767
780
  }
768
781
  else {
769
- handleEnd(eventData);
782
+ if (Math.abs(velocity) > longPressRatio) {
783
+ // 超过速度阈值,按照实时方向(快速来回滑动)
784
+ handleEnd(realtimeData);
785
+ }
786
+ else {
787
+ // 超过速度阈值,按照起始方向(慢速长按)
788
+ handleBack(originData);
789
+ }
770
790
  }
771
791
  })
772
792
  .withRef(swiperGestureRef);
@@ -487,7 +487,7 @@ export function useTransformStyle(styleObj = {}, { enableVar, transformRadiusPer
487
487
  // transform rpx to px
488
488
  transformBoxShadow(normalStyle);
489
489
  // transform 字符串格式转化数组格式(先转数组再处理css var)
490
- transformTransform(styleObj);
490
+ transformTransform(normalStyle);
491
491
  return {
492
492
  hasVarDec,
493
493
  varContextRef,
@@ -637,9 +637,13 @@ export function getCurrentPage(pageId) {
637
637
  const pages = global.getCurrentPages();
638
638
  return pages.find((page) => isFunction(page.getPageId) && page.getPageId() === pageId);
639
639
  }
640
- export function renderImage(imageProps, enableFastImage = false) {
641
- // eslint-disable-next-line @typescript-eslint/no-var-requires
642
- const Component = enableFastImage ? require('@d11/react-native-fast-image') : Image;
640
+ export function renderImage(imageProps, enableFastImage = true) {
641
+ let Component = Image;
642
+ if (enableFastImage) {
643
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
644
+ const fastImageModule = require('@d11/react-native-fast-image');
645
+ Component = fastImageModule.default || fastImageModule;
646
+ }
643
647
  return createElement(Component, imageProps);
644
648
  }
645
649
  export function pickStyle(styleObj = {}, pickedKeys, callback) {
@@ -82,7 +82,8 @@ const DefaultFallback = ({ onReload }: DefaultFallbackProps) => {
82
82
 
83
83
  const DefaultLoading = () => {
84
84
  // eslint-disable-next-line @typescript-eslint/no-var-requires
85
- const FastImage = require('@d11/react-native-fast-image')
85
+ const FastImageModule = require('@d11/react-native-fast-image')
86
+ const FastImage = FastImageModule.default || FastImageModule
86
87
  return (
87
88
  <View style={styles.container}>
88
89
  <FastImage