@mpxjs/webpack-plugin 2.9.59 → 2.9.62

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 (106) hide show
  1. package/lib/platform/style/wx/index.js +314 -254
  2. package/lib/platform/template/wx/component-config/checkbox-group.js +8 -0
  3. package/lib/platform/template/wx/component-config/checkbox.js +8 -0
  4. package/lib/platform/template/wx/component-config/cover-image.js +15 -0
  5. package/lib/platform/template/wx/component-config/cover-view.js +9 -0
  6. package/lib/platform/template/wx/component-config/form.js +13 -1
  7. package/lib/platform/template/wx/component-config/icon.js +8 -0
  8. package/lib/platform/template/wx/component-config/index.js +5 -1
  9. package/lib/platform/template/wx/component-config/label.js +15 -0
  10. package/lib/platform/template/wx/component-config/movable-area.js +18 -1
  11. package/lib/platform/template/wx/component-config/movable-view.js +18 -1
  12. package/lib/platform/template/wx/component-config/navigator.js +8 -0
  13. package/lib/platform/template/wx/component-config/picker-view-column.js +8 -0
  14. package/lib/platform/template/wx/component-config/picker-view.js +18 -2
  15. package/lib/platform/template/wx/component-config/picker.js +14 -1
  16. package/lib/platform/template/wx/component-config/radio-group.js +8 -0
  17. package/lib/platform/template/wx/component-config/radio.js +8 -0
  18. package/lib/platform/template/wx/component-config/root-portal.js +15 -0
  19. package/lib/platform/template/wx/component-config/switch.js +8 -0
  20. package/lib/platform/template/wx/component-config/unsupported.js +1 -3
  21. package/lib/react/processScript.js +2 -0
  22. package/lib/runtime/components/react/context.ts +38 -0
  23. package/lib/runtime/components/react/dist/context.js +7 -0
  24. package/lib/runtime/components/react/dist/getInnerListeners.js +22 -11
  25. package/lib/runtime/components/react/dist/mpx-button.jsx +67 -45
  26. package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +81 -0
  27. package/lib/runtime/components/react/dist/mpx-checkbox.jsx +152 -0
  28. package/lib/runtime/components/react/dist/mpx-form.jsx +59 -0
  29. package/lib/runtime/components/react/dist/mpx-icon.jsx +51 -0
  30. package/lib/runtime/components/react/dist/mpx-image/index.jsx +17 -22
  31. package/lib/runtime/components/react/dist/mpx-image/svg.jsx +0 -1
  32. package/lib/runtime/components/react/dist/mpx-input.jsx +38 -16
  33. package/lib/runtime/components/react/dist/mpx-label.jsx +63 -0
  34. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +46 -0
  35. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +346 -0
  36. package/lib/runtime/components/react/dist/mpx-navigator.jsx +35 -0
  37. package/lib/runtime/components/react/dist/mpx-picker/date.jsx +69 -0
  38. package/lib/runtime/components/react/dist/mpx-picker/index.jsx +138 -0
  39. package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +139 -0
  40. package/lib/runtime/components/react/dist/mpx-picker/region.jsx +90 -0
  41. package/lib/runtime/components/react/dist/mpx-picker/regionData.js +6099 -0
  42. package/lib/runtime/components/react/dist/mpx-picker/selector.jsx +76 -0
  43. package/lib/runtime/components/react/dist/mpx-picker/time.jsx +244 -0
  44. package/lib/runtime/components/react/dist/mpx-picker/type.js +1 -0
  45. package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +15 -0
  46. package/lib/runtime/components/react/dist/mpx-picker-view.jsx +68 -0
  47. package/lib/runtime/components/react/dist/mpx-radio-group.jsx +79 -0
  48. package/lib/runtime/components/react/dist/mpx-radio.jsx +169 -0
  49. package/lib/runtime/components/react/dist/mpx-root-portal.jsx +11 -0
  50. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +66 -50
  51. package/lib/runtime/components/react/dist/mpx-swiper/carouse.jsx +206 -147
  52. package/lib/runtime/components/react/dist/mpx-swiper/index.jsx +9 -7
  53. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +3 -3
  54. package/lib/runtime/components/react/dist/mpx-switch.jsx +76 -0
  55. package/lib/runtime/components/react/dist/mpx-text.jsx +7 -19
  56. package/lib/runtime/components/react/dist/mpx-textarea.jsx +1 -1
  57. package/lib/runtime/components/react/dist/mpx-view.jsx +326 -96
  58. package/lib/runtime/components/react/dist/mpx-web-view.jsx +9 -15
  59. package/lib/runtime/components/react/dist/types/common.js +1 -0
  60. package/lib/runtime/components/react/dist/useNodesRef.js +3 -8
  61. package/lib/runtime/components/react/dist/utils.js +82 -14
  62. package/lib/runtime/components/react/getInnerListeners.ts +25 -13
  63. package/lib/runtime/components/react/mpx-button.tsx +87 -67
  64. package/lib/runtime/components/react/mpx-checkbox-group.tsx +147 -0
  65. package/lib/runtime/components/react/mpx-checkbox.tsx +245 -0
  66. package/lib/runtime/components/react/mpx-form.tsx +89 -0
  67. package/lib/runtime/components/react/mpx-icon.tsx +103 -0
  68. package/lib/runtime/components/react/mpx-image/index.tsx +20 -32
  69. package/lib/runtime/components/react/mpx-image/svg.tsx +2 -2
  70. package/lib/runtime/components/react/mpx-input.tsx +54 -26
  71. package/lib/runtime/components/react/mpx-label.tsx +115 -0
  72. package/lib/runtime/components/react/mpx-movable-area.tsx +67 -0
  73. package/lib/runtime/components/react/mpx-movable-view.tsx +425 -0
  74. package/lib/runtime/components/react/mpx-navigator.tsx +67 -0
  75. package/lib/runtime/components/react/mpx-picker/date.tsx +83 -0
  76. package/lib/runtime/components/react/mpx-picker/index.tsx +155 -0
  77. package/lib/runtime/components/react/mpx-picker/multiSelector.tsx +153 -0
  78. package/lib/runtime/components/react/mpx-picker/region.tsx +104 -0
  79. package/lib/runtime/components/react/mpx-picker/regionData.ts +6101 -0
  80. package/lib/runtime/components/react/mpx-picker/selector.tsx +92 -0
  81. package/lib/runtime/components/react/mpx-picker/time.tsx +274 -0
  82. package/lib/runtime/components/react/mpx-picker/type.ts +107 -0
  83. package/lib/runtime/components/react/mpx-picker-view-column.tsx +28 -0
  84. package/lib/runtime/components/react/mpx-picker-view.tsx +104 -0
  85. package/lib/runtime/components/react/mpx-radio-group.tsx +147 -0
  86. package/lib/runtime/components/react/mpx-radio.tsx +246 -0
  87. package/lib/runtime/components/react/mpx-root-portal.tsx +25 -0
  88. package/lib/runtime/components/react/mpx-scroll-view.tsx +82 -58
  89. package/lib/runtime/components/react/mpx-swiper/carouse.tsx +203 -156
  90. package/lib/runtime/components/react/mpx-swiper/index.tsx +12 -13
  91. package/lib/runtime/components/react/mpx-swiper/type.ts +11 -4
  92. package/lib/runtime/components/react/mpx-swiper-item.tsx +5 -3
  93. package/lib/runtime/components/react/mpx-switch.tsx +127 -0
  94. package/lib/runtime/components/react/mpx-text.tsx +52 -68
  95. package/lib/runtime/components/react/mpx-textarea.tsx +2 -2
  96. package/lib/runtime/components/react/mpx-view.tsx +373 -140
  97. package/lib/runtime/components/react/mpx-web-view.tsx +24 -28
  98. package/lib/runtime/components/react/types/common.ts +12 -0
  99. package/lib/runtime/components/react/types/getInnerListeners.ts +2 -1
  100. package/lib/runtime/components/react/types/global.d.ts +4 -0
  101. package/lib/runtime/components/react/useNodesRef.ts +3 -8
  102. package/lib/runtime/components/react/utils.ts +93 -15
  103. package/lib/runtime/optionProcessor.js +19 -17
  104. package/lib/template-compiler/compiler.js +56 -41
  105. package/lib/template-compiler/gen-node-react.js +7 -7
  106. package/package.json +6 -3
@@ -4,49 +4,55 @@
4
4
  * ✔ hover-start-time
5
5
  * ✔ hover-stay-time
6
6
  */
7
- import { View, Text, StyleProp, TextStyle, ViewStyle, NativeSyntheticEvent, ViewProps, ImageStyle, ImageResizeMode, StyleSheet, Image, LayoutChangeEvent } from 'react-native'
7
+ import { View, Text, StyleProp, TextStyle, NativeSyntheticEvent, ViewProps, ImageStyle, ImageResizeMode, StyleSheet, Image, LayoutChangeEvent } from 'react-native'
8
8
  import { useRef, useState, useEffect, forwardRef, ReactNode, JSX } from 'react'
9
- // @ts-ignore
10
9
  import useInnerProps from './getInnerListeners'
11
- // @ts-ignore
12
- import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数
13
-
14
- import { parseUrl, TEXT_STYLE_REGEX, PERCENT_REGEX, isText} from './utils'
15
-
16
-
17
- type ExtendedViewStyle = ViewStyle & {
18
- backgroundImage?: string
19
- backgroundSize?: ImageResizeMode
20
- }
10
+ import { ExtendedViewStyle } from './types/common'
11
+ import useNodesRef, { HandlerRef } from './useNodesRef'
21
12
 
13
+ import { parseUrl, PERCENT_REGEX, isText, every, normalizeStyle, splitStyle, splitProps, throwReactWarning, transformTextStyle } from './utils'
22
14
  export interface _ViewProps extends ViewProps {
23
- style?: Array<ExtendedViewStyle>
24
- children?: ReactNode | ReactNode []
25
- hoverStyle: Array<ExtendedViewStyle>
26
- ['hover-start-time']: number
27
- ['hover-stay-time']: number
15
+ style?: ExtendedViewStyle
16
+ children?: ReactNode | ReactNode[]
17
+ hoverStyle?: ExtendedViewStyle
18
+ ['hover-start-time']?: number
19
+ ['hover-stay-time']?: number
28
20
  'enable-offset'?: boolean
21
+ 'enable-background-image'?: boolean
29
22
  bindtouchstart?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
30
23
  bindtouchmove?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
31
24
  bindtouchend?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
32
25
  }
33
26
 
34
- type Obj = Record<string, any>
35
-
36
- type GroupData = Record<string, Record<string, any>>;
37
-
38
- type Handler = (...args: any []) => void
27
+ type Handler = (...args: any[]) => void
39
28
 
40
29
  type Size = {
41
- width: number,
30
+ width: number
42
31
  height: number
43
32
  }
44
33
 
45
34
  type DimensionValue = number | 'auto' | `${number}%`
46
35
 
36
+ type Position = {
37
+ left?: number
38
+ right?: number
39
+ top?: number
40
+ bottom?: number
41
+ }
42
+
43
+ type PositionKey = keyof Position
44
+
45
+ type NumberVal = number | `${number}%`
46
+
47
+ type PositionVal = PositionKey | NumberVal
48
+
49
+ type backgroundPositionList = ['left' | 'right', NumberVal, 'top' | 'bottom', NumberVal] | []
50
+
47
51
  type PreImageInfo = {
48
52
  src?: string,
49
- sizeList: DimensionValue []
53
+ sizeList: DimensionValue[]
54
+ containPercentSymbol?: boolean
55
+ backgroundPosition: backgroundPositionList
50
56
  }
51
57
 
52
58
  type ImageProps = {
@@ -54,98 +60,150 @@ type ImageProps = {
54
60
  src?: string
55
61
  }
56
62
 
57
- const IMAGE_STYLE_REGEX = /^background(Image|Size|Repeat|Position)$/
58
-
59
- function groupBy(style: Obj, callback: (key: string, val: string) => string, group:GroupData = {}):GroupData {
60
- let groupKey = ''
61
- for (let key in style) {
62
- if (style.hasOwnProperty(key)) { // 确保处理对象自身的属性
63
- let val: string = style[key] as string
64
- groupKey = callback(key, val)
65
- if (!group[groupKey]) {
66
- group[groupKey] = {}
67
- }
68
- group[groupKey][key] = val
69
- }
70
- }
71
- return group
72
- }
73
-
74
- const applyHandlers = (handlers: Handler[] , args: any [] ) => {
63
+ const applyHandlers = (handlers: Handler[], args: any[]) => {
75
64
  for (let handler of handlers) {
76
65
  handler(...args)
77
66
  }
78
67
  }
79
68
 
80
- const checkNeedLayout = (style: PreImageInfo) => {
69
+ const checkNeedLayout = (style: PreImageInfo) => {
81
70
  const [width, height] = style.sizeList
82
- return (PERCENT_REGEX.test(`${height}`) && width === 'auto') || (PERCENT_REGEX.test(`${width}`) && height === 'auto')
71
+ const bp = style.backgroundPosition
72
+ // 含有百分号,center 需计算布局
73
+ const containPercentSymbol = typeof bp[1] === 'string' && PERCENT_REGEX.test(bp[1]) || typeof bp[3] === 'string' && PERCENT_REGEX.test(bp[3])
74
+
75
+ return {
76
+ // 是否开启layout的计算
77
+ needLayout: typeof width === 'string' && /^cover|contain$/.test(width) || (typeof height === 'string' && PERCENT_REGEX.test(height) && width === 'auto') || (typeof width === 'string' && PERCENT_REGEX.test(width) && height === 'auto') || containPercentSymbol,
78
+ // 是否开启原始宽度的计算
79
+ needImageSize: typeof width === 'string' && /^cover|contain$/.test(width) || style.sizeList.includes('auto')
80
+ }
83
81
  }
84
82
 
85
83
  /**
86
- * h - 用户设置的高度
87
- * lh - 容器的高度
88
- * ratio - 原始图片的宽高比
89
- * **/
90
- function calculateSize(h: number, lh: number, ratio: number) {
91
- let height, width
92
- if (PERCENT_REGEX.test(`${h}`)) { // auto px/rpx
84
+ * h - 用户设置的高度
85
+ * lh - 容器的高度
86
+ * ratio - 原始图片的宽高比
87
+ * **/
88
+ function calculateSize(h: number, ratio: number, lh?: number | boolean, reverse: boolean = false): Size | null {
89
+ let height = 0, width = 0
90
+
91
+ if (typeof lh === 'boolean') {
92
+ reverse = lh
93
+ }
94
+
95
+ if (typeof h === 'string' && PERCENT_REGEX.test(h)) { // auto px/rpx
93
96
  if (!lh) return null
94
- height = (parseFloat(`${h}`) / 100) * lh
97
+ height = (parseFloat(h) / 100) * (lh as number)
95
98
  width = height * ratio
96
99
  } else { // 2. auto px/rpx - 根据比例计算
97
100
  height = h
98
101
  width = height * ratio
99
102
  }
100
-
101
103
  return {
102
- width,
103
- height
104
+ width: reverse ? height : width,
105
+ height: reverse ? width : height
106
+ }
107
+ }
108
+
109
+ /**
110
+ * 用户设置百分比后,转换为偏移量
111
+ * h - 用户设置图片的高度
112
+ * ch - 容器的高度
113
+ * val - 用户设置的百分比
114
+ * **/
115
+ function calculateSizePosition(h: number, ch: number, val: string): number {
116
+ if (!h || !ch) return 0
117
+
118
+ // 百分比需要单独的计算
119
+ if (typeof h === 'string' && PERCENT_REGEX.test(h)) {
120
+ h = ch * parseFloat(h) / 100
104
121
  }
122
+
123
+ // (container width - image width) * (position x%) = (x offset value)
124
+ return (ch - h) * parseFloat(val) / 100
125
+ }
126
+
127
+ function backgroundPosition(imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) {
128
+ const bps = preImageInfo.backgroundPosition
129
+ if (bps.length === 0) return
130
+ let style: Position = {}
131
+ let imageStyle: ImageStyle = imageProps.style || {}
132
+
133
+ for (let i = 0; i < bps.length; i += 2) {
134
+ let key = bps[i] as PositionKey, val = bps[i + 1]
135
+ // 需要获取 图片宽度 和 容器的宽度 进行计算
136
+ if (typeof val === 'string' && PERCENT_REGEX.test(val)) {
137
+ if (i === 0) {
138
+ style[key] = calculateSizePosition(imageStyle.width as number, layoutInfo?.width, val)
139
+ } else {
140
+ style[key] = calculateSizePosition(imageStyle.height as number, layoutInfo?.height, val)
141
+ }
142
+ } else {
143
+ style[key] = val as number
144
+ }
145
+ }
146
+
147
+ imageProps.style = {
148
+ ...imageProps.style as ImageStyle,
149
+ ...style
150
+ }
151
+
105
152
  }
106
153
 
107
154
  // background-size 转换
108
- function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) {
155
+ function backgroundSize(imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) {
109
156
  let sizeList = preImageInfo.sizeList
110
157
  if (!sizeList) return
158
+ const { width: layoutWidth, height: layoutHeight } = layoutInfo || {}
159
+ const { width: imageSizeWidth, height: imageSizeHeight } = imageSize || {}
160
+ const [width, height] = sizeList
161
+ let dimensions: {
162
+ width: NumberVal,
163
+ height: NumberVal
164
+ } | null = { width: 0, height: 0 }
165
+
111
166
  // 枚举值
112
- if (['cover', 'contain'].includes(`${sizeList[0]}`)) {
113
- imageProps.style.resizeMode = sizeList[0] as ImageResizeMode
167
+ if (typeof width === 'string' && ['cover', 'contain'].includes(width)) {
168
+ if (layoutInfo && imageSize) {
169
+ let layoutRatio = layoutWidth / imageSizeWidth
170
+ let eleRatio = imageSizeWidth / imageSizeHeight
171
+ // 容器宽高比 大于 图片的宽高比,依据宽度作为基准,否则以高度为基准
172
+ if (layoutRatio <= eleRatio && (width as string) === 'contain' || layoutRatio >= eleRatio && (width as string) === 'cover') {
173
+ dimensions = calculateSize(layoutWidth as number, imageSizeHeight / imageSizeWidth, true) as Size
174
+ } else if (layoutRatio > eleRatio && (width as string) === 'contain' || layoutRatio < eleRatio && (width as string) === 'cover') {
175
+ dimensions = calculateSize(layoutHeight as number, imageSizeWidth / imageSizeHeight) as Size
176
+ }
177
+ }
114
178
  } else {
115
- const [width, height] = sizeList
116
- let newWidth: ImageStyle['width'] = 0, newHeight: ImageStyle['height'] = 0
117
-
118
- const { width: imageSizeWidth, height: imageSizeHeight } = imageSize || {}
119
-
120
179
  if (width === 'auto' && height === 'auto') { // 均为auto
121
180
  if (!imageSize) return
122
- newHeight = imageSizeHeight
123
- newWidth = imageSizeWidth
181
+ dimensions = {
182
+ width: imageSizeWidth,
183
+ height: imageSizeHeight
184
+ }
124
185
  } else if (width === 'auto') { // auto px/rpx/%
125
186
  if (!imageSize) return
126
- const dimensions = calculateSize(height as number, layoutInfo?.height, imageSizeWidth / imageSizeHeight)
187
+ dimensions = calculateSize(height as number, imageSizeWidth / imageSizeHeight, layoutInfo?.height)
127
188
  if (!dimensions) return
128
- newWidth = dimensions.width
129
- newHeight = dimensions.height
130
- }else if (height === 'auto') { // auto px/rpx/%
189
+ } else if (height === 'auto') { // auto px/rpx/%
131
190
  if (!imageSize) return
132
- const dimensions = calculateSize(width as number, layoutInfo?.width, imageSizeHeight / imageSizeWidth)
191
+ dimensions = calculateSize(width as number, imageSizeHeight / imageSizeWidth, layoutInfo?.width, true)
133
192
  if (!dimensions) return
134
- newHeight = dimensions.width
135
- newWidth = dimensions.height
136
193
  } else { // 数值类型 ImageStyle
137
194
  // 数值类型设置为 stretch
138
195
  (imageProps.style as ImageStyle).resizeMode = 'stretch'
139
- newWidth = PERCENT_REGEX.test(`${width}`) ? width : +width! as DimensionValue
140
- newHeight = PERCENT_REGEX.test(`${width}`) ? height : +height! as DimensionValue
141
- }
142
- // 样式合并
143
- imageProps.style = {
144
- ...imageProps.style as ImageStyle,
145
- width: newWidth,
146
- height: newHeight
196
+ dimensions = {
197
+ width: typeof width === 'string' && PERCENT_REGEX.test(width) ? width : +width! as number,
198
+ height: typeof height === 'string' && PERCENT_REGEX.test(height) ? height : +height! as number
199
+ }
147
200
  }
148
201
  }
202
+ // 样式合并
203
+ imageProps.style = {
204
+ ...imageProps.style as ImageStyle,
205
+ ...dimensions
206
+ }
149
207
  }
150
208
 
151
209
  // background-image转换为source
@@ -158,28 +216,102 @@ const imageStyleToProps = (preImageInfo: PreImageInfo, imageSize: Size, layoutIn
158
216
  const imageProps: ImageProps = {
159
217
  style: {
160
218
  resizeMode: 'cover' as ImageResizeMode,
161
- ...StyleSheet.absoluteFillObject
219
+ position: 'absolute'
220
+ // ...StyleSheet.absoluteFillObject
162
221
  }
163
222
  }
164
-
165
- applyHandlers([ backgroundSize, backgroundImage ],[imageProps, preImageInfo, imageSize, layoutInfo])
223
+ applyHandlers([backgroundSize, backgroundImage, backgroundPosition], [imageProps, preImageInfo, imageSize, layoutInfo])
166
224
  if (!imageProps?.src) return null
167
225
  return imageProps
168
226
  }
169
227
 
228
+ function isHorizontal(val: PositionVal): val is 'left' | 'right' {
229
+ return typeof val === 'string' && /^(left|right)$/.test(val)
230
+ }
231
+
232
+ function isVertical(val: PositionVal): val is 'top' | 'bottom' {
233
+ return typeof val === 'string' && /^(top|bottom)$/.test(val)
234
+ }
235
+
236
+ function normalizeBackgroundPosition(parts: PositionVal[]): backgroundPositionList {
237
+
238
+ if (parts.length === 0) return []
239
+
240
+ // 定义默认值
241
+ let hStart: 'left' | 'right' = 'left'
242
+ let hOffset: PositionVal = 0
243
+ let vStart: 'top' | 'bottom' = 'top'
244
+ let vOffset: PositionVal = 0
245
+
246
+ if (parts.length === 4) return parts as backgroundPositionList
247
+
248
+ // 归一化
249
+ if (parts.length === 1) {
250
+ // 1. center
251
+ // 2. 2px - hOffset, vOffset(center) - center为50%
252
+ // 3. 10% - hOffset, vOffset(center) - center为50%
253
+ // 4. left - hStart, vOffset(center) - center为50%
254
+ // 5. top - hOffset(center), vStart - center为50%
255
+
256
+ if (isHorizontal(parts[0])) {
257
+ hStart = parts[0]
258
+ vOffset = '50%'
259
+ } else if (isVertical(parts[0])) {
260
+ vStart = parts[0]
261
+ hOffset = '50%'
262
+ } else {
263
+ hOffset = parts[0]
264
+ vOffset = '50%'
265
+ }
266
+ } else if (parts.length === 2) {
267
+ // 1. center center - hOffset, vOffset
268
+ // 2. 10px center - hOffset, vStart
269
+ // 3. left center - hStart, vOffset
270
+ // 4. right center - hStart, vOffset
271
+ // 5. 第一位是 left right 覆盖的是 hStart
272
+ // center, 100% 正常的px 覆盖的是 hOffset
273
+ // 第二位是 top bottom 覆盖的是 vStart
274
+ // center, 100% 覆盖的是 vOffset
275
+ //
276
+ // 水平方向
277
+ if (isHorizontal(parts[0])) {
278
+ hStart = parts[0]
279
+ } else { // center, 100% 正常的px 覆盖的是 hOffset
280
+ hOffset = parts[0]
281
+ }
282
+ // 垂直方向
283
+ if (isVertical(parts[1])) {
284
+ vStart = parts[1]
285
+ } else { // center, 100% 正常的px 覆盖的是 hOffset
286
+ vOffset = parts[1]
287
+ }
288
+ } else if (parts.length === 3) {
289
+ // 1. center top 10px / top 10px center 等价 - center为50%
290
+ // 2. right 10px center / center right 10px 等价 - center为50%
291
+ // 2. bottom 50px right
292
+ if (typeof parts[0] === 'string' && typeof parts[1] === 'string' && /^left|bottom|right|top$/.test(parts[0]) && /^left|bottom|right|top$/.test(parts[1])) {
293
+ [hStart, vStart, vOffset] = parts as ['left' | 'right', 'top' | 'bottom', number]
294
+ } else {
295
+ [hStart, hOffset, vStart] = parts as ['left' | 'right', number, 'top' | 'bottom']
296
+ }
297
+ }
298
+
299
+ return [hStart, hOffset, vStart, vOffset] as backgroundPositionList
300
+ }
170
301
 
171
302
  function preParseImage(imageStyle?: ExtendedViewStyle) {
172
303
 
173
- const { backgroundImage, backgroundSize = [ "auto" ] } = imageStyle || {}
304
+ const { backgroundImage, backgroundSize = ['auto'], backgroundPosition = [0, 0] } = imageStyle || {}
174
305
  const src = parseUrl(backgroundImage)
175
306
 
176
- let sizeList = backgroundSize.slice() as DimensionValue []
307
+ let sizeList = backgroundSize.slice() as DimensionValue[]
177
308
 
178
- sizeList.length === 1 && sizeList.push(sizeList[0])
309
+ sizeList.length === 1 && sizeList.push('auto')
179
310
 
180
311
  return {
181
312
  src,
182
- sizeList
313
+ sizeList,
314
+ backgroundPosition: normalizeBackgroundPosition(backgroundPosition)
183
315
  }
184
316
  }
185
317
 
@@ -195,10 +327,9 @@ function wrapImage(imageStyle?: ExtendedViewStyle) {
195
327
  // 预解析
196
328
  const preImageInfo: PreImageInfo = preParseImage(imageStyle)
197
329
 
198
-
199
330
  // 判断是否可挂载onLayout
200
- const needLayout = checkNeedLayout(preImageInfo)
201
- const { src, sizeList } = preImageInfo
331
+ const { needLayout, needImageSize } = checkNeedLayout(preImageInfo)
332
+ const { src } = preImageInfo
202
333
 
203
334
  useEffect(() => {
204
335
  if(!src) {
@@ -207,8 +338,8 @@ function wrapImage(imageStyle?: ExtendedViewStyle) {
207
338
  layoutInfo.current = null
208
339
  return
209
340
  }
210
-
211
- if (!sizeList.includes('auto')) {
341
+
342
+ if (!needImageSize) {
212
343
  setShow(true)
213
344
  return
214
345
  }
@@ -232,71 +363,97 @@ function wrapImage(imageStyle?: ExtendedViewStyle) {
232
363
 
233
364
  if (!preImageInfo?.src) return null
234
365
 
235
- const onLayout = (res: LayoutChangeEvent) => {
366
+ const onLayout = (res: LayoutChangeEvent ) => {
236
367
  const { width, height } = res?.nativeEvent?.layout || {}
237
368
  layoutInfo.current = {
238
369
  width,
239
370
  height
240
371
  }
241
- if (sizeInfo.current) {
242
- setImageSizeWidth(sizeInfo.current.width)
243
- setImageSizeHeight(sizeInfo.current.height)
372
+ if (!needImageSize) {
244
373
  setLayoutInfoWidth(width)
245
- setLayoutInfoHeight(height)
374
+ setLayoutInfoHeight(height)
375
+ } else if (sizeInfo.current) {
376
+ setLayoutInfoWidth(width)
377
+ setLayoutInfoHeight(height)
378
+ setImageSizeWidth(sizeInfo.current.width)
379
+ setImageSizeHeight(sizeInfo.current.height)
246
380
  setShow(true)
247
381
  }
248
382
  }
249
-
250
- return <View key='viewBgImg' {...needLayout ? {onLayout} : null } style={{ ...StyleSheet.absoluteFillObject, width: '100%', height: '100%', overflow: 'hidden'}}>
251
- {show && <Image {...imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size)} />}
252
- </View>
253
- }
254
383
 
255
- function splitStyle(styles: ExtendedViewStyle) {
256
- return groupBy(styles, (key) => {
257
- if (TEXT_STYLE_REGEX.test(key))
258
- return 'textStyle'
259
- else if (IMAGE_STYLE_REGEX.test(key)) return 'imageStyle'
260
- return 'innerStyle'
261
- }, {})
384
+ return <View key='viewBgImg' {...needLayout ? { onLayout } : null } style={{ ...StyleSheet.absoluteFillObject, width: '100%', height: '100%', overflow: 'hidden' }}>
385
+ {show && <Image {...imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size)} />}
386
+ </View>
262
387
  }
263
388
 
264
- function every(children: ReactNode [], callback: (children: ReactNode) => boolean) {
265
- return children.every((child) => callback(child))
266
- }
389
+ function wrapChildren(children: ReactNode | ReactNode[], props: _ViewProps, textStyle?: StyleProp<TextStyle>, imageStyle?: ExtendedViewStyle) {
390
+ const { textProps } = splitProps(props)
391
+ const { 'enable-background-image': enableBackgroundImage } = props
267
392
 
268
- function wrapChildren(children: ReactNode | ReactNode [] , textStyle?: StyleProp<TextStyle>, imageStyle?: ExtendedViewStyle) {
269
- children = Array.isArray(children) ? children : [children]
270
- if (every(children as ReactNode[], (child)=>isText(child))) {
271
- children = [<Text key='viewTextWrap' style={textStyle}>{children}</Text>]
393
+ if (every(children as ReactNode[], (child) => isText(child))) {
394
+ if (textStyle || textProps) {
395
+ transformTextStyle(textStyle as TextStyle)
396
+ children = <Text key='viewTextWrap' style={textStyle} {...(textProps || {})}>{children}</Text>
397
+ }
272
398
  } else {
273
- if(textStyle) console.warn('Text style will be ignored unless every child of the view is Text node!')
399
+ if (textStyle) throwReactWarning('[Mpx runtime warn]: Text style will be ignored unless every child of the view is Text node!')
274
400
  }
275
401
 
276
402
  return [
277
- wrapImage(imageStyle),
278
- ...children
403
+ enableBackgroundImage ? wrapImage(imageStyle) : null,
404
+ children
279
405
  ]
280
406
  }
281
407
 
282
408
  const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref): JSX.Element => {
409
+ const combinationStyleProps = [{
410
+ key: 'transform',
411
+ rules: {
412
+ width: 'translateX',
413
+ height: 'translateY'
414
+ }
415
+ }, {
416
+ key: 'borderTopLeftRadius',
417
+ rules: {
418
+ width: 'borderTopLeftRadius'
419
+ }
420
+ }, {
421
+ key: 'borderBottomLeftRadius',
422
+ rules: {
423
+ width: 'borderBottomLeftRadius'
424
+ }
425
+ }, {
426
+ key: 'borderBottomRightRadius',
427
+ rules: {
428
+ height: 'borderBottomRightRadius'
429
+ }
430
+ }, {
431
+ key: 'borderTopRightRadius',
432
+ rules: {
433
+ height: 'borderTopRightRadius'
434
+ }
435
+ }]
283
436
  const {
284
- style = [],
437
+ style = {},
285
438
  children,
286
439
  hoverStyle,
287
440
  'hover-start-time': hoverStartTime = 50,
288
441
  'hover-stay-time': hoverStayTime = 400,
289
- 'enable-offset': enableOffset
442
+ 'enable-offset': enableOffset,
290
443
  } = props
291
444
 
292
445
  const [isHover, setIsHover] = useState(false)
446
+ let transformStyle = {}
447
+
448
+ const [containerWidth, setContainerWidth] = useState(0)
449
+ const [containerHeight, setContainerHeight] = useState(0)
293
450
 
294
451
  const layoutRef = useRef({})
295
452
 
296
453
  // 打平 style 数组
297
- const styleObj:ExtendedViewStyle = StyleSheet.flatten(style)
454
+ const styleObj: ExtendedViewStyle = normalizeStyle(style)
298
455
  // 默认样式
299
- const defaultStyle:ExtendedViewStyle = {
456
+ const defaultStyle: ExtendedViewStyle = {
300
457
  // flex 布局相关的默认样式
301
458
  ...styleObj.display === 'flex' && {
302
459
  flexDirection: 'row',
@@ -306,6 +463,24 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
306
463
  }
307
464
  }
308
465
 
466
+ const hasPercentStyle = combinationStyleProps.some(({ key, rules }) => {
467
+ return Object.entries(rules).some(([dimension, transformKey]) => {
468
+ const transformItemValue = styleObj[key]
469
+ if (transformItemValue) {
470
+ if (Array.isArray(transformItemValue)) {
471
+ const transformValue = transformItemValue.find((item: Record<string, any>) => item.hasOwnProperty(transformKey))
472
+ return transformValue && PERCENT_REGEX.test(transformValue[transformKey])
473
+ } else if (typeof transformItemValue === 'string') {
474
+ return PERCENT_REGEX.test(transformItemValue)
475
+ }
476
+ }
477
+ })
478
+ })
479
+
480
+ if (hasPercentStyle) {
481
+ transformStyle = percentTransform(combinationStyleProps, { width: containerWidth, height: containerHeight })
482
+ }
483
+
309
484
  const { nodeRef } = useNodesRef<View, _ViewProps>(props, ref, {
310
485
  defaultStyle
311
486
  })
@@ -325,7 +500,7 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
325
500
  const setStartTimer = () => {
326
501
  dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer)
327
502
  dataRef.current.startTimer = setTimeout(() => {
328
- setIsHover(() => true)
503
+ setIsHover(true)
329
504
  }, +hoverStartTime)
330
505
  }
331
506
 
@@ -333,38 +508,95 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
333
508
  dataRef.current.stayTimer && clearTimeout(dataRef.current.stayTimer)
334
509
  dataRef.current.startTimer && clearTimeout(dataRef.current.startTimer)
335
510
  dataRef.current.stayTimer = setTimeout(() => {
336
- setIsHover(() => false)
511
+ setIsHover(false)
337
512
  }, +hoverStayTime)
338
513
  }
339
514
 
340
- function onTouchStart(e: NativeSyntheticEvent<TouchEvent>){
515
+ function onTouchStart(e: NativeSyntheticEvent<TouchEvent>) {
341
516
  const { bindtouchstart } = props;
342
517
  bindtouchstart && bindtouchstart(e)
343
518
  setStartTimer()
344
519
  }
345
520
 
346
- function onTouchEnd(e: NativeSyntheticEvent<TouchEvent>){
521
+ function onTouchEnd(e: NativeSyntheticEvent<TouchEvent>) {
347
522
  const { bindtouchend } = props;
348
523
  bindtouchend && bindtouchend(e)
349
524
  setStayTimer()
350
525
  }
351
526
 
352
- const onLayout = () => {
353
-
354
- nodeRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => {
355
- layoutRef.current = { x, y, width, height, offsetLeft, offsetTop }
527
+
528
+ function percentTransform(style: string[] | Record<string, any>, { width, height }: { width?: number, height?: number }) {
529
+ const styleMap: Record<string, any> = {}
530
+ style.forEach((styleItem: Record<string, any>) => {
531
+ const transformItemValue = styleObj[styleItem.key]
532
+ if (Array.isArray(transformItemValue)) {
533
+ const transformStyle: Record<string, any>[] = []
534
+ styleObj[styleItem.key].forEach((transformItem: Record<string, any>) => {
535
+ const rules = styleItem.rules
536
+ for (const type in rules) {
537
+ const value = transformItem[rules[type]]
538
+ if (value !== undefined) {
539
+ if (PERCENT_REGEX.test(value)) {
540
+ const percentage = parseFloat(value) / 100;
541
+ if (type === 'height' && height) {
542
+ transformStyle.push({ [rules[type]]: percentage * height });
543
+ } else if (type === 'width' && width) {
544
+ transformStyle.push({ [rules[type]]: percentage * width });
545
+ } else {
546
+ transformStyle.push({ [rules[type]]: 0 });
547
+ }
548
+ } else {
549
+ transformStyle.push(transformItem);
550
+ }
551
+ }
552
+ }
553
+ })
554
+ styleMap[styleItem.key] = transformStyle
555
+ } else if (typeof transformItemValue === 'string') {
556
+ const rules = styleItem.rules
557
+ for (const type in rules) {
558
+ if (transformItemValue) {
559
+ if (PERCENT_REGEX.test(transformItemValue)) {
560
+ const percentage = parseFloat(transformItemValue) / 100;
561
+ if (type === 'height' && height) {
562
+ styleMap[styleItem.key] = percentage * height
563
+ } else if (type === 'width' && width) {
564
+ styleMap[styleItem.key] = percentage * width
565
+ } else {
566
+ styleMap[styleItem.key] = 0
567
+ }
568
+ } else {
569
+ styleMap[styleItem.key] = transformItemValue
570
+ }
571
+ }
572
+ }
573
+ }
356
574
  })
575
+ return styleMap
357
576
  }
577
+ const onLayout = (res: LayoutChangeEvent) => {
578
+ if (hasPercentStyle) {
579
+ const { width, height } = res?.nativeEvent?.layout || {}
580
+ setContainerWidth(width || 0)
581
+ setContainerHeight(height || 0)
582
+ }
583
+ if (enableOffset) {
584
+ nodeRef.current?.measure((x: number, y: number, width: number, height: number, offsetLeft: number, offsetTop: number) => {
585
+ layoutRef.current = { x, y, width, height, offsetLeft, offsetTop }
586
+ })
587
+ }
588
+ }
589
+ const { textStyle, imageStyle, innerStyle } = splitStyle({
590
+ ...defaultStyle,
591
+ ...styleObj,
592
+ ...(isHover ? hoverStyle : null)
593
+ })
358
594
 
359
- const {textStyle, imageStyle, innerStyle} = splitStyle(StyleSheet.flatten<ExtendedViewStyle>([
360
- defaultStyle,
361
- styleObj,
362
- ...(isHover ? hoverStyle : [])]
363
- ))
595
+ const needLayout = enableOffset || hasPercentStyle
364
596
 
365
597
  const innerProps = useInnerProps(props, {
366
598
  ref: nodeRef,
367
- ...enableOffset ? { onLayout } : {},
599
+ ...needLayout ? { onLayout } : {},
368
600
  ...(hoverStyle && {
369
601
  bindtouchstart: onTouchStart,
370
602
  bindtouchend: onTouchEnd
@@ -376,7 +608,8 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
376
608
  'hover-stay-time',
377
609
  'hoverStyle',
378
610
  'hover-class',
379
- 'enable-offset'
611
+ 'enable-offset',
612
+ 'enable-background-image'
380
613
  ], {
381
614
  layoutRef
382
615
  })
@@ -384,9 +617,9 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
384
617
  return (
385
618
  <View
386
619
  {...innerProps}
387
- style={innerStyle}
620
+ style={{ ...innerStyle, ...transformStyle }}
388
621
  >
389
- {wrapChildren(children, textStyle, imageStyle)}
622
+ {wrapChildren(children, props, textStyle, imageStyle)}
390
623
  </View>
391
624
  )
392
625
  })