@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,327 @@
1
+ import React, { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect } from 'react'
2
+ import { useTransformStyle, useLayout, extendObject } from './utils'
3
+ import useInnerProps, { getCustomEvent } from './getInnerListeners'
4
+ import { noop } from '@mpxjs/utils'
5
+ import { RouteContext } from './context'
6
+
7
+ const qualityValue = {
8
+ high: 90,
9
+ normal: 75,
10
+ low: 50,
11
+ original: 100
12
+ }
13
+
14
+ interface CameraProps {
15
+ mode?: 'normal' | 'scanCode'
16
+ resolution?: 'low' | 'medium' | 'high'
17
+ devicePosition?: 'front' | 'back'
18
+ flash?: 'auto' | 'on' | 'off'
19
+ frameSize?: 'small' | 'medium' | 'large'
20
+ style?: Record<string, any>
21
+ bindstop?: () => void
22
+ binderror?: (error: { message: string }) => void
23
+ bindinitdone?: (result: { type: string, data: string }) => void
24
+ bindscancode?: (result: { type: string, data: string }) => void
25
+ 'parent-font-size'?: number
26
+ 'parent-width'?: number
27
+ 'parent-height'?: number
28
+ 'enable-var'?: boolean
29
+ 'external-var-context'?: any
30
+ }
31
+
32
+ interface TakePhotoOptions {
33
+ quality?: 'high' | 'normal' | 'low' | 'original'
34
+ success?: (result: { errMsg: string, tempImagePath: string }) => void
35
+ fail?: (result: { errMsg: string }) => void
36
+ complete?: (result: { errMsg: string, tempImagePath?: string }) => void
37
+ }
38
+
39
+ interface RecordOptions {
40
+ timeout?: number
41
+ success?: (result: { errMsg: string }) => void
42
+ fail?: (result: { errMsg: string, error?: any }) => void
43
+ complete?: (result: { errMsg: string }) => void
44
+ timeoutCallback?: (result: { errMsg: string, error?: any }) => void
45
+ }
46
+
47
+ interface StopRecordOptions {
48
+ success?: (result: { errMsg: string, tempVideoPath: string, duration: number }) => void
49
+ fail?: (result: { errMsg: string }) => void
50
+ complete?: (result: { errMsg: string, tempVideoPath?: string, duration?: number }) => void
51
+ }
52
+
53
+ interface CameraRef {
54
+ setZoom: (zoom: number) => void
55
+ takePhoto: (options?: TakePhotoOptions) => void
56
+ startRecord: (options?: RecordOptions) => void
57
+ stopRecord: (options?: StopRecordOptions) => void
58
+ }
59
+
60
+ type HandlerRef<T, P> = {
61
+ current: T | null
62
+ }
63
+
64
+ let RecordRes: any = null
65
+
66
+ const _camera = forwardRef<HandlerRef<any, CameraProps>, CameraProps>((props: CameraProps, ref): JSX.Element | null => {
67
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
68
+ const { Camera, useCameraDevice, useCodeScanner, useCameraFormat } = require('react-native-vision-camera')
69
+ const cameraRef = useRef<any>(null)
70
+ const {
71
+ mode = 'normal',
72
+ resolution = 'medium',
73
+ devicePosition = 'back',
74
+ flash = 'auto',
75
+ frameSize = 'medium',
76
+ bindinitdone,
77
+ bindstop,
78
+ bindscancode,
79
+ 'parent-font-size': parentFontSize,
80
+ 'parent-width': parentWidth,
81
+ 'parent-height': parentHeight,
82
+ 'enable-var': enableVar,
83
+ 'external-var-context': externalVarContext,
84
+ style = {}
85
+ } = props
86
+ const styleObj = extendObject(
87
+ {},
88
+ style
89
+ )
90
+ const {
91
+ normalStyle,
92
+ hasSelfPercent,
93
+ setWidth,
94
+ setHeight
95
+ } = useTransformStyle(styleObj, {
96
+ enableVar,
97
+ externalVarContext,
98
+ parentFontSize,
99
+ parentWidth,
100
+ parentHeight
101
+ })
102
+ const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: cameraRef })
103
+ const isPhoto = mode === 'normal'
104
+ const device = useCameraDevice(devicePosition || 'back')
105
+ const { navigation } = useContext(RouteContext) || {}
106
+ const [zoomValue, setZoomValue] = useState<number>(1)
107
+ const [hasPermission, setHasPermission] = useState<boolean | null>(null)
108
+ const hasCamera = useRef(false)
109
+
110
+ // 先定义常量,避免在条件判断后使用
111
+ const maxZoom = device?.maxZoom || 1
112
+ const RESOLUTION_MAPPING: Record<string, { width: number, height: number }> = {
113
+ low: { width: 640, height: 480 },
114
+ medium: { width: 1280, height: 720 },
115
+ high: { width: 1920, height: 1080 }
116
+ }
117
+ const FRAME_SIZE_MAPPING: Record<string, { width: number, height: number }> = {
118
+ small: { width: 480, height: 360 },
119
+ medium: { width: 720, height: 540 },
120
+ large: { width: 1080, height: 810 }
121
+ }
122
+
123
+ const format = useCameraFormat(device, [
124
+ {
125
+ photoResolution: RESOLUTION_MAPPING[resolution],
126
+ videoResolution: FRAME_SIZE_MAPPING[frameSize] || RESOLUTION_MAPPING[resolution]
127
+ }
128
+ ])
129
+
130
+ const codeScanner = useCodeScanner({
131
+ codeTypes: ['qr', 'ean-13'],
132
+ onCodeScanned: (codes: any[]) => {
133
+ const result = codes.map(code => code.value).join(',')
134
+ bindscancode && bindscancode(getCustomEvent('scancode', {}, {
135
+ detail: {
136
+ result: codes.map(code => code.value).join(',')
137
+ }
138
+ }))
139
+ }
140
+ })
141
+
142
+ const onInitialized = useCallback(() => {
143
+ bindinitdone && bindinitdone(getCustomEvent('initdone', {}, {
144
+ detail: {
145
+ maxZoom
146
+ }
147
+ }))
148
+ }, [bindinitdone, maxZoom])
149
+
150
+ const onStopped = useCallback(() => {
151
+ bindstop && bindstop()
152
+ }, [bindstop])
153
+
154
+ const camera: CameraRef = {
155
+ setZoom: (zoom: number) => {
156
+ setZoomValue(zoom)
157
+ },
158
+ takePhoto: (options: TakePhotoOptions = {}) => {
159
+ const { success = noop, fail = noop, complete = noop } = options
160
+ cameraRef.current?.takePhoto?.({
161
+ flash,
162
+ quality: qualityValue[options.quality || 'normal'] as number
163
+ } as any).then((res: { path: any }) => {
164
+ const result = {
165
+ errMsg: 'takePhoto:ok',
166
+ tempImagePath: res.path
167
+ }
168
+ success(result)
169
+ complete(result)
170
+ }).catch(() => {
171
+ const result = {
172
+ errMsg: 'takePhoto:fail'
173
+ }
174
+ fail(result)
175
+ complete(result)
176
+ })
177
+ },
178
+ startRecord: (options: RecordOptions = {}) => {
179
+ let { timeout = 30, success = noop, fail = noop, complete = noop, timeoutCallback = noop } = options
180
+ timeout = timeout > 300 ? 300 : timeout
181
+ let recordTimer: NodeJS.Timeout | null = null
182
+ let isTimeout = false
183
+ try {
184
+ const result = {
185
+ errMsg: 'startRecord:ok'
186
+ }
187
+ success(result)
188
+ complete(result)
189
+
190
+ cameraRef.current?.startRecording?.({
191
+ flash,
192
+ onRecordingError: (error: any) => {
193
+ if (recordTimer) clearTimeout(recordTimer)
194
+ const errorResult = {
195
+ errMsg: 'startRecord:fail during recording',
196
+ error: error
197
+ }
198
+ timeoutCallback(errorResult)
199
+ },
200
+ onRecordingFinished: (video: any) => {
201
+ RecordRes = video
202
+ if (recordTimer) clearTimeout(recordTimer)
203
+ }
204
+ })
205
+
206
+ recordTimer = setTimeout(() => { // 超时自动停止
207
+ isTimeout = true
208
+ cameraRef.current?.stopRecording().catch(() => {
209
+ // 忽略停止录制时的错误
210
+ })
211
+ }, timeout * 1000)
212
+ } catch (error: any) {
213
+ if (recordTimer) clearTimeout(recordTimer)
214
+ const result = {
215
+ errMsg: 'startRecord:fail ' + (error.message || 'unknown error')
216
+ }
217
+ fail(result)
218
+ complete(result)
219
+ }
220
+ },
221
+ stopRecord: (options: StopRecordOptions = {}) => {
222
+ const { success = noop, fail = noop, complete = noop } = options
223
+ try {
224
+ cameraRef.current?.stopRecording().then(() => {
225
+ setTimeout(() => {
226
+ if (RecordRes) {
227
+ const result = {
228
+ errMsg: 'stopRecord:ok',
229
+ tempVideoPath: RecordRes?.path,
230
+ duration: RecordRes.duration * 1000 // 转成ms
231
+ }
232
+ RecordRes = null
233
+ success(result)
234
+ complete(result)
235
+ }
236
+ }, 200) // 延时200ms,确保录制结果已准备好
237
+ }).catch((e: any) => {
238
+ const result = {
239
+ errMsg: 'stopRecord:fail ' + (e.message || 'promise rejected')
240
+ }
241
+ fail(result)
242
+ complete(result)
243
+ })
244
+ } catch (error: any) {
245
+ const result = {
246
+ errMsg: 'stopRecord:fail ' + (error.message || 'unknown error')
247
+ }
248
+ fail(result)
249
+ complete(result)
250
+ }
251
+ }
252
+ }
253
+
254
+ useEffect(() => {
255
+ if (navigation) {
256
+ if (navigation && !navigation.camera) {
257
+ navigation.camera = camera
258
+ } else {
259
+ hasCamera.current = true
260
+ navigation.camera.multi = true
261
+ }
262
+ }
263
+ const checkCameraPermission = async () => {
264
+ try {
265
+ const cameraPermission = global?.__mpx?.config?.rnConfig?.cameraPermission
266
+ if (typeof cameraPermission === 'function') {
267
+ const permissionResult = await cameraPermission()
268
+ setHasPermission(permissionResult === true)
269
+ } else {
270
+ setHasPermission(true)
271
+ }
272
+ } catch (error) {
273
+ setHasPermission(false)
274
+ }
275
+ }
276
+ checkCameraPermission()
277
+ return () => {
278
+ if (navigation && navigation.camera) {
279
+ navigation.camera = null
280
+ }
281
+ }
282
+ }, [])
283
+
284
+ const innerProps = useInnerProps(
285
+ extendObject(
286
+ {},
287
+ props,
288
+ layoutProps,
289
+ {
290
+ ref: cameraRef,
291
+ style: extendObject({}, normalStyle, layoutStyle),
292
+ isActive: true,
293
+ photo: true,
294
+ video: true,
295
+ onInitialized,
296
+ onStopped,
297
+ device,
298
+ format,
299
+ codeScanner: !isPhoto ? codeScanner : undefined,
300
+ zoom: zoomValue
301
+ }
302
+ ),
303
+ [
304
+ 'mode',
305
+ 'resolution',
306
+ 'frame-size',
307
+ 'bindinitdone',
308
+ 'bindstop',
309
+ 'flash',
310
+ 'bindscancode',
311
+ 'binderror'
312
+ ],
313
+ {
314
+ layoutRef
315
+ }
316
+ )
317
+
318
+ if (!hasPermission || hasCamera.current || !device) {
319
+ return null
320
+ }
321
+
322
+ return createElement(Camera, innerProps)
323
+ })
324
+
325
+ _camera.displayName = 'MpxCamera'
326
+
327
+ export default _camera
@@ -54,7 +54,7 @@ import {
54
54
  NativeTouchEvent
55
55
  } from 'react-native'
56
56
  import { warn } from '@mpxjs/utils'
57
- import { useUpdateEffect, useTransformStyle, useLayout, extendObject, isIOS } from './utils'
57
+ import { useUpdateEffect, useTransformStyle, useLayout, extendObject, isAndroid } from './utils'
58
58
  import useInnerProps, { getCustomEvent } from './getInnerListeners'
59
59
  import useNodesRef, { HandlerRef } from './useNodesRef'
60
60
  import { FormContext, FormFieldValue, KeyboardAvoidContext } from './context'
@@ -188,6 +188,8 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
188
188
  }
189
189
 
190
190
  const defaultValue = parseValue(value)
191
+ // 微信小程序的 input 永远是单行,textAlignVertical 固定为 auto
192
+ // multiline 为 true 时表示是 textarea 组件复用此逻辑
191
193
  const textAlignVertical = multiline ? 'top' : 'auto'
192
194
  const isAutoFocus = !!autoFocus || !!focus
193
195
 
@@ -467,6 +469,12 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
467
469
  : (nodeRef.current as TextInput)?.blur()
468
470
  }, [isAutoFocus])
469
471
 
472
+ // 使用 multiline 来修复光标位置问题
473
+ // React Native 的 TextInput 在 textAlign center + placeholder 时光标会跑到右边
474
+ // 这个问题只在 Android 上出现
475
+ // 参考:https://github.com/facebook/react-native/issues/28794 (Android only)
476
+ const needMultilineFix = isAndroid && !multiline
477
+
470
478
  const innerProps = useInnerProps(
471
479
  extendObject(
472
480
  {},
@@ -490,7 +498,7 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
490
498
  underlineColorAndroid: 'rgba(0,0,0,0)',
491
499
  textAlignVertical: textAlignVertical,
492
500
  placeholderTextColor: placeholderStyle?.color,
493
- multiline: !!multiline,
501
+ multiline: multiline || needMultilineFix,
494
502
  onTouchStart,
495
503
  onTouchEnd,
496
504
  onFocus,
@@ -500,6 +508,7 @@ const Input = forwardRef<HandlerRef<TextInput, FinalInputProps>, FinalInputProps
500
508
  onContentSizeChange,
501
509
  onSubmitEditing: bindconfirm && onSubmitEditing
502
510
  },
511
+ needMultilineFix ? { numberOfLines: 1 } : {},
503
512
  !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }
504
513
  ),
505
514
  [
@@ -23,6 +23,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
23
23
  // 比如机型 iPhone 11 Pro,可能会导致显隐动画冲突
24
24
  // 因此增加状态标记 + cancelAnimation 来优化
25
25
  const isShow = useRef<boolean>(false)
26
+ const keybaordHandleTimerRef = useRef<NodeJS.Timeout | null>(null)
26
27
 
27
28
  const animatedStyle = useAnimatedStyle(() => ({
28
29
  // translate/position top+ overflow hidden 在 android 上时因为键盘顶起让页面高度变小,同时元素位置上移
@@ -79,7 +80,7 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
79
80
  // 重置标记位
80
81
  keyboardAvoid.current.readyToShow = false
81
82
  }
82
- if (!keyboardAvoid?.current || isShow.current) {
83
+ if (!keyboardAvoid?.current) {
83
84
  return
84
85
  }
85
86
 
@@ -124,13 +125,23 @@ const KeyboardAvoidingView = ({ children, style, contentContainerStyle }: Keyboa
124
125
  }
125
126
 
126
127
  if (isIOS) {
127
- subscriptions = [Keyboard.addListener('keyboardWillShow', keybaordAvoding), Keyboard.addListener('keyboardWillHide', resetKeyboard)]
128
+ subscriptions = [
129
+ Keyboard.addListener('keyboardWillShow', (evt: any) => {
130
+ if (keybaordHandleTimerRef.current) {
131
+ clearTimeout(keybaordHandleTimerRef.current)
132
+ }
133
+ // iphone 在input聚焦时长按滑动后会导致 show 事件先于 focus 事件发生,因此等一下,等 focus 先触发拿到 input,避免键盘出现但input没顶上去
134
+ keybaordHandleTimerRef.current = setTimeout(() => keybaordAvoding(evt), 32)
135
+ }),
136
+ Keyboard.addListener('keyboardWillHide', resetKeyboard)
137
+ ]
128
138
  } else {
129
139
  subscriptions = [Keyboard.addListener('keyboardDidShow', keybaordAvoding), Keyboard.addListener('keyboardDidHide', resetKeyboard)]
130
140
  }
131
141
 
132
142
  return () => {
133
143
  subscriptions.forEach(subscription => subscription.remove())
144
+ keybaordHandleTimerRef.current && clearTimeout(keybaordHandleTimerRef.current)
134
145
  }
135
146
  }, [keyboardAvoid])
136
147