@mpxjs/webpack-plugin 2.9.62 → 2.9.64

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 (86) hide show
  1. package/lib/index.js +1 -3
  2. package/lib/platform/style/wx/index.js +67 -53
  3. package/lib/react/processStyles.js +1 -0
  4. package/lib/react/processTemplate.js +2 -3
  5. package/lib/react/style-helper.js +12 -7
  6. package/lib/runtime/components/react/context.ts +9 -7
  7. package/lib/runtime/components/react/dist/context.js +1 -0
  8. package/lib/runtime/components/react/dist/getInnerListeners.js +12 -1
  9. package/lib/runtime/components/react/dist/mpx-button.jsx +52 -74
  10. package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +19 -18
  11. package/lib/runtime/components/react/dist/mpx-checkbox.jsx +28 -41
  12. package/lib/runtime/components/react/dist/mpx-form.jsx +16 -14
  13. package/lib/runtime/components/react/dist/mpx-icon.jsx +14 -17
  14. package/lib/runtime/components/react/dist/mpx-image/index.jsx +34 -33
  15. package/lib/runtime/components/react/dist/mpx-image/svg.jsx +3 -1
  16. package/lib/runtime/components/react/dist/mpx-input.jsx +35 -31
  17. package/lib/runtime/components/react/dist/mpx-label.jsx +29 -37
  18. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +13 -18
  19. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +8 -8
  20. package/lib/runtime/components/react/dist/mpx-picker/index.jsx +9 -9
  21. package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +7 -4
  22. package/lib/runtime/components/react/dist/mpx-picker/region.jsx +11 -7
  23. package/lib/runtime/components/react/dist/mpx-picker/selector.jsx +1 -1
  24. package/lib/runtime/components/react/dist/mpx-picker/time.jsx +18 -18
  25. package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +102 -10
  26. package/lib/runtime/components/react/dist/mpx-picker-view.jsx +147 -53
  27. package/lib/runtime/components/react/dist/mpx-radio-group.jsx +19 -18
  28. package/lib/runtime/components/react/dist/mpx-radio.jsx +28 -43
  29. package/lib/runtime/components/react/dist/mpx-root-portal.jsx +8 -4
  30. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +33 -26
  31. package/lib/runtime/components/react/dist/mpx-swiper/carouse.jsx +139 -74
  32. package/lib/runtime/components/react/dist/mpx-swiper/index.jsx +14 -6
  33. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +19 -11
  34. package/lib/runtime/components/react/dist/mpx-switch.jsx +17 -14
  35. package/lib/runtime/components/react/dist/mpx-text.jsx +19 -35
  36. package/lib/runtime/components/react/dist/mpx-textarea.jsx +1 -1
  37. package/lib/runtime/components/react/dist/mpx-view.jsx +284 -209
  38. package/lib/runtime/components/react/dist/mpx-web-view.jsx +8 -5
  39. package/lib/runtime/components/react/dist/parser.js +218 -0
  40. package/lib/runtime/components/react/dist/utils.jsx +433 -0
  41. package/lib/runtime/components/react/getInnerListeners.ts +18 -8
  42. package/lib/runtime/components/react/mpx-button.tsx +81 -91
  43. package/lib/runtime/components/react/mpx-checkbox-group.tsx +48 -43
  44. package/lib/runtime/components/react/mpx-checkbox.tsx +52 -63
  45. package/lib/runtime/components/react/mpx-form.tsx +49 -21
  46. package/lib/runtime/components/react/mpx-icon.tsx +30 -27
  47. package/lib/runtime/components/react/mpx-image/index.tsx +52 -46
  48. package/lib/runtime/components/react/mpx-image/svg.tsx +5 -3
  49. package/lib/runtime/components/react/mpx-input.tsx +58 -38
  50. package/lib/runtime/components/react/mpx-label.tsx +54 -59
  51. package/lib/runtime/components/react/mpx-movable-area.tsx +38 -24
  52. package/lib/runtime/components/react/mpx-movable-view.tsx +27 -28
  53. package/lib/runtime/components/react/mpx-navigator.tsx +2 -2
  54. package/lib/runtime/components/react/mpx-picker/date.tsx +2 -3
  55. package/lib/runtime/components/react/mpx-picker/index.tsx +10 -10
  56. package/lib/runtime/components/react/mpx-picker/multiSelector.tsx +15 -12
  57. package/lib/runtime/components/react/mpx-picker/region.tsx +21 -18
  58. package/lib/runtime/components/react/mpx-picker/selector.tsx +5 -6
  59. package/lib/runtime/components/react/mpx-picker/time.tsx +25 -29
  60. package/lib/runtime/components/react/mpx-picker/type.ts +1 -1
  61. package/lib/runtime/components/react/mpx-picker-view-column.tsx +148 -20
  62. package/lib/runtime/components/react/mpx-picker-view.tsx +179 -63
  63. package/lib/runtime/components/react/mpx-radio-group.tsx +50 -47
  64. package/lib/runtime/components/react/mpx-radio.tsx +56 -72
  65. package/lib/runtime/components/react/mpx-root-portal.tsx +10 -8
  66. package/lib/runtime/components/react/mpx-scroll-view.tsx +133 -103
  67. package/lib/runtime/components/react/mpx-swiper/carouse.tsx +174 -96
  68. package/lib/runtime/components/react/mpx-swiper/index.tsx +18 -9
  69. package/lib/runtime/components/react/mpx-swiper/type.ts +16 -5
  70. package/lib/runtime/components/react/mpx-swiper-item.tsx +46 -13
  71. package/lib/runtime/components/react/mpx-switch.tsx +44 -23
  72. package/lib/runtime/components/react/mpx-text.tsx +37 -45
  73. package/lib/runtime/components/react/mpx-textarea.tsx +1 -1
  74. package/lib/runtime/components/react/mpx-view.tsx +388 -240
  75. package/lib/runtime/components/react/mpx-web-view.tsx +19 -20
  76. package/lib/runtime/components/react/parser.ts +245 -0
  77. package/lib/runtime/components/react/types/common.ts +4 -4
  78. package/lib/runtime/components/react/types/global.d.ts +14 -2
  79. package/lib/runtime/components/react/useNodesRef.ts +1 -2
  80. package/lib/runtime/components/react/utils.tsx +505 -0
  81. package/lib/template-compiler/compiler.js +28 -20
  82. package/lib/template-compiler/gen-node-react.js +1 -3
  83. package/lib/web/processStyles.js +2 -5
  84. package/package.json +6 -4
  85. package/lib/runtime/components/react/dist/utils.js +0 -148
  86. package/lib/runtime/components/react/utils.ts +0 -170
@@ -4,21 +4,26 @@
4
4
  * ✔ hover-start-time
5
5
  * ✔ hover-stay-time
6
6
  */
7
- import { View, Text, StyleProp, TextStyle, NativeSyntheticEvent, ViewProps, ImageStyle, ImageResizeMode, StyleSheet, Image, LayoutChangeEvent } from 'react-native'
8
- import { useRef, useState, useEffect, forwardRef, ReactNode, JSX } from 'react'
7
+ import { View, TextStyle, NativeSyntheticEvent, ViewProps, ImageStyle, ImageResizeMode, StyleSheet, Image, LayoutChangeEvent, Text } from 'react-native'
8
+ import { useRef, useState, useEffect, forwardRef, ReactNode, JSX, Children, cloneElement } from 'react'
9
9
  import useInnerProps from './getInnerListeners'
10
10
  import { ExtendedViewStyle } from './types/common'
11
11
  import useNodesRef, { HandlerRef } from './useNodesRef'
12
+ import { parseUrl, PERCENT_REGEX, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout } from './utils'
13
+ import LinearGradient from 'react-native-linear-gradient'
12
14
 
13
- import { parseUrl, PERCENT_REGEX, isText, every, normalizeStyle, splitStyle, splitProps, throwReactWarning, transformTextStyle } from './utils'
14
15
  export interface _ViewProps extends ViewProps {
15
16
  style?: ExtendedViewStyle
16
17
  children?: ReactNode | ReactNode[]
17
- hoverStyle?: ExtendedViewStyle
18
- ['hover-start-time']?: number
19
- ['hover-stay-time']?: number
20
- 'enable-offset'?: boolean
21
- 'enable-background-image'?: boolean
18
+ 'hover-style'?: ExtendedViewStyle
19
+ 'hover-start-time'?: number
20
+ 'hover-stay-time'?: number
21
+ 'enable-background'?: boolean
22
+ 'enable-var'?: boolean
23
+ 'external-var-context'?: Record<string, any>
24
+ 'parent-font-size'?: number
25
+ 'parent-width'?: number
26
+ 'parent-height'?: number
22
27
  bindtouchstart?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
23
28
  bindtouchmove?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
24
29
  bindtouchend?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
@@ -31,7 +36,7 @@ type Size = {
31
36
  height: number
32
37
  }
33
38
 
34
- type DimensionValue = number | 'auto' | `${number}%`
39
+ type DimensionValue = number | `${number}%` | 'auto' | 'contain' | 'cover'
35
40
 
36
41
  type Position = {
37
42
  left?: number
@@ -48,35 +53,94 @@ type PositionVal = PositionKey | NumberVal
48
53
 
49
54
  type backgroundPositionList = ['left' | 'right', NumberVal, 'top' | 'bottom', NumberVal] | []
50
55
 
56
+ type LinearInfo = {
57
+ colors: Array<string>,
58
+ locations: Array<number>,
59
+ direction?: string
60
+ }
61
+
51
62
  type PreImageInfo = {
52
63
  src?: string,
53
64
  sizeList: DimensionValue[]
54
- containPercentSymbol?: boolean
65
+ type?: 'image' | 'linear'
66
+ linearInfo?: LinearInfo
67
+ // containPercentSymbol?: boolean
55
68
  backgroundPosition: backgroundPositionList
56
69
  }
57
70
 
58
71
  type ImageProps = {
59
72
  style: ImageStyle,
60
- src?: string
73
+ src?: string,
74
+ colors: Array<string>,
75
+ locations?: Array<number>
76
+ angle?: number
77
+ }
78
+
79
+ const linearMap = new Map([
80
+ ['top', 0],
81
+ ['bottom', 180],
82
+ ['left', 270],
83
+ ['right', 90]
84
+ ])
85
+
86
+ // 对角线角度
87
+ const diagonalAngleMap: Record<string, (width: number, height: number) => any> = {
88
+ 'top right': (width: number, height: number) => {
89
+ return Math.acos(
90
+ (width / 2) /
91
+ (Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) / 2)
92
+ )
93
+ },
94
+ 'right top': (width, height) => { return diagonalAngleMap['top right'](width, height) },
95
+
96
+ 'bottom right': (width, height) => Math.PI - diagonalAngleMap['top right'](width, height),
97
+ 'right bottom': (width, height) => { return diagonalAngleMap['bottom right'](width, height) },
98
+
99
+ 'bottom left': (width, height) => Math.PI + diagonalAngleMap['top right'](width, height),
100
+ 'left bottom': (width, height) => { return diagonalAngleMap['bottom left'](width, height) },
101
+
102
+ 'top left': (width, height) => (2 * Math.PI) - diagonalAngleMap['top right'](width, height),
103
+ 'left top': (width, height) => { return diagonalAngleMap['top left'](width, height) }
104
+ }
105
+
106
+ // 弧度转化为角度的公式
107
+ function radToAngle (r: number) {
108
+ return r * 180 / Math.PI
61
109
  }
62
110
 
63
111
  const applyHandlers = (handlers: Handler[], args: any[]) => {
64
- for (let handler of handlers) {
112
+ for (const handler of handlers) {
65
113
  handler(...args)
66
114
  }
67
115
  }
68
116
 
69
- const checkNeedLayout = (style: PreImageInfo) => {
70
- const [width, height] = style.sizeList
71
- const bp = style.backgroundPosition
117
+ const isPercent = (val: string | number | undefined): val is string => typeof val === 'string' && PERCENT_REGEX.test(val)
118
+
119
+ const isBackgroundSizeKeyword = (val: string | number): boolean => typeof val === 'string' && /^cover|contain$/.test(val)
120
+
121
+ const isNeedLayout = (preImageInfo: PreImageInfo): boolean => {
122
+ const { sizeList, backgroundPosition, linearInfo } = preImageInfo
123
+ const [width, height] = sizeList
124
+ const bp = backgroundPosition
125
+
72
126
  // 含有百分号,center 需计算布局
73
- const containPercentSymbol = typeof bp[1] === 'string' && PERCENT_REGEX.test(bp[1]) || typeof bp[3] === 'string' && PERCENT_REGEX.test(bp[3])
127
+ return isBackgroundSizeKeyword(width) ||
128
+ (isPercent(height) && width === 'auto') ||
129
+ (isPercent(width) && height === 'auto') ||
130
+ isPercent(bp[1]) ||
131
+ isPercent(bp[3]) ||
132
+ isDiagonalAngle(linearInfo)
133
+ }
74
134
 
135
+ const checkNeedLayout = (preImageInfo: PreImageInfo) => {
136
+ const { sizeList } = preImageInfo
137
+ const [width] = sizeList
138
+ // 在渐变的时候,background-size的cover,contain, auto属性值,转化为100%, needLayout计算逻辑和原来保持一致,needImageSize始终为false
75
139
  return {
76
140
  // 是否开启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,
141
+ needLayout: isNeedLayout(preImageInfo),
78
142
  // 是否开启原始宽度的计算
79
- needImageSize: typeof width === 'string' && /^cover|contain$/.test(width) || style.sizeList.includes('auto')
143
+ needImageSize: isBackgroundSizeKeyword(width) || sizeList.includes('auto')
80
144
  }
81
145
  }
82
146
 
@@ -85,14 +149,14 @@ const checkNeedLayout = (style: PreImageInfo) => {
85
149
  * lh - 容器的高度
86
150
  * ratio - 原始图片的宽高比
87
151
  * **/
88
- function calculateSize(h: number, ratio: number, lh?: number | boolean, reverse: boolean = false): Size | null {
89
- let height = 0, width = 0
152
+ function calculateSize (h: number, ratio: number, lh?: number | boolean, reverse = false): Size | null {
153
+ let height = 0; let width = 0
90
154
 
91
155
  if (typeof lh === 'boolean') {
92
156
  reverse = lh
93
157
  }
94
158
 
95
- if (typeof h === 'string' && PERCENT_REGEX.test(h)) { // auto px/rpx
159
+ if (isPercent(h)) { // auto px/rpx
96
160
  if (!lh) return null
97
161
  height = (parseFloat(h) / 100) * (lh as number)
98
162
  width = height * ratio
@@ -112,11 +176,11 @@ function calculateSize(h: number, ratio: number, lh?: number | boolean, reverse:
112
176
  * ch - 容器的高度
113
177
  * val - 用户设置的百分比
114
178
  * **/
115
- function calculateSizePosition(h: number, ch: number, val: string): number {
179
+ function calculateSizePosition (h: number, ch: number, val: string): number {
116
180
  if (!h || !ch) return 0
117
181
 
118
182
  // 百分比需要单独的计算
119
- if (typeof h === 'string' && PERCENT_REGEX.test(h)) {
183
+ if (isPercent(h)) {
120
184
  h = ch * parseFloat(h) / 100
121
185
  }
122
186
 
@@ -124,16 +188,25 @@ function calculateSizePosition(h: number, ch: number, val: string): number {
124
188
  return (ch - h) * parseFloat(val) / 100
125
189
  }
126
190
 
127
- function backgroundPosition(imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) {
191
+ /**
192
+ * 获取图片的展示宽高
193
+ * h - 用户设置的高度
194
+ * lh - 容器的高度
195
+ * **/
196
+ const calcPercent = (h: NumberVal, lh: number) => {
197
+ return isPercent(h) ? parseFloat(h) / 100 * lh : +h
198
+ }
199
+
200
+ function backgroundPosition (imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) {
128
201
  const bps = preImageInfo.backgroundPosition
129
202
  if (bps.length === 0) return
130
- let style: Position = {}
131
- let imageStyle: ImageStyle = imageProps.style || {}
203
+ const style: Position = {}
204
+ const imageStyle: ImageStyle = imageProps.style || {}
132
205
 
133
206
  for (let i = 0; i < bps.length; i += 2) {
134
- let key = bps[i] as PositionKey, val = bps[i + 1]
207
+ const key = bps[i] as PositionKey; const val = bps[i + 1]
135
208
  // 需要获取 图片宽度 和 容器的宽度 进行计算
136
- if (typeof val === 'string' && PERCENT_REGEX.test(val)) {
209
+ if (isPercent(val)) {
137
210
  if (i === 0) {
138
211
  style[key] = calculateSizePosition(imageStyle.width as number, layoutInfo?.width, val)
139
212
  } else {
@@ -148,12 +221,11 @@ function backgroundPosition(imageProps: ImageProps, preImageInfo: PreImageInfo,
148
221
  ...imageProps.style as ImageStyle,
149
222
  ...style
150
223
  }
151
-
152
224
  }
153
225
 
154
226
  // background-size 转换
155
- function backgroundSize(imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) {
156
- let sizeList = preImageInfo.sizeList
227
+ function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) {
228
+ const sizeList = preImageInfo.sizeList
157
229
  if (!sizeList) return
158
230
  const { width: layoutWidth, height: layoutHeight } = layoutInfo || {}
159
231
  const { width: imageSizeWidth, height: imageSizeHeight } = imageSize || {}
@@ -166,12 +238,12 @@ function backgroundSize(imageProps: ImageProps, preImageInfo: PreImageInfo, imag
166
238
  // 枚举值
167
239
  if (typeof width === 'string' && ['cover', 'contain'].includes(width)) {
168
240
  if (layoutInfo && imageSize) {
169
- let layoutRatio = layoutWidth / imageSizeWidth
170
- let eleRatio = imageSizeWidth / imageSizeHeight
241
+ const layoutRatio = layoutWidth / imageSizeWidth
242
+ const eleRatio = imageSizeWidth / imageSizeHeight
171
243
  // 容器宽高比 大于 图片的宽高比,依据宽度作为基准,否则以高度为基准
172
- if (layoutRatio <= eleRatio && (width as string) === 'contain' || layoutRatio >= eleRatio && (width as string) === 'cover') {
244
+ if ((layoutRatio <= eleRatio && (width as string) === 'contain') || (layoutRatio >= eleRatio && (width as string) === 'cover')) {
173
245
  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') {
246
+ } else if ((layoutRatio > eleRatio && (width as string) === 'contain') || (layoutRatio < eleRatio && (width as string) === 'cover')) {
175
247
  dimensions = calculateSize(layoutHeight as number, imageSizeWidth / imageSizeHeight) as Size
176
248
  }
177
249
  }
@@ -194,9 +266,9 @@ function backgroundSize(imageProps: ImageProps, preImageInfo: PreImageInfo, imag
194
266
  // 数值类型设置为 stretch
195
267
  (imageProps.style as ImageStyle).resizeMode = 'stretch'
196
268
  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
- }
269
+ width: isPercent(width) ? width : +width,
270
+ height: isPercent(height) ? height : +height
271
+ } as { width: NumberVal, height: NumberVal }
200
272
  }
201
273
  }
202
274
  // 样式合并
@@ -207,8 +279,32 @@ function backgroundSize(imageProps: ImageProps, preImageInfo: PreImageInfo, imag
207
279
  }
208
280
 
209
281
  // background-image转换为source
210
- function backgroundImage(imageProps: ImageProps, preImageInfo: PreImageInfo) {
211
- imageProps.src = preImageInfo.src
282
+ function backgroundImage (imageProps: ImageProps, preImageInfo: PreImageInfo) {
283
+ if (preImageInfo.src) {
284
+ imageProps.src = preImageInfo.src
285
+ }
286
+ }
287
+
288
+ // 渐变的转换
289
+ function linearGradient (imageProps: ImageProps, preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) {
290
+ const { type, linearInfo } = preImageInfo
291
+ const { colors = [], locations, direction = '' } = linearInfo || {}
292
+ const { width, height } = imageSize || {}
293
+
294
+ if (type !== 'linear') return
295
+
296
+ // 角度计算
297
+ let angle = +(linearMap.get(direction) || direction.match(/(-?\d+(\.\d+)?)deg/)?.[1] || 180) % 360
298
+
299
+ // 对角线角度计算
300
+ if (layoutInfo && diagonalAngleMap[direction] && imageSize && linearInfo) {
301
+ angle = radToAngle(diagonalAngleMap[direction](width, height)) || 180
302
+ }
303
+
304
+ // 赋值
305
+ imageProps.colors = colors
306
+ imageProps.locations = locations
307
+ imageProps.angle = angle
212
308
  }
213
309
 
214
310
  const imageStyleToProps = (preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) => {
@@ -218,23 +314,23 @@ const imageStyleToProps = (preImageInfo: PreImageInfo, imageSize: Size, layoutIn
218
314
  resizeMode: 'cover' as ImageResizeMode,
219
315
  position: 'absolute'
220
316
  // ...StyleSheet.absoluteFillObject
221
- }
317
+ },
318
+ colors: []
222
319
  }
223
- applyHandlers([backgroundSize, backgroundImage, backgroundPosition], [imageProps, preImageInfo, imageSize, layoutInfo])
224
- if (!imageProps?.src) return null
320
+ applyHandlers([backgroundSize, backgroundImage, backgroundPosition, linearGradient], [imageProps, preImageInfo, imageSize, layoutInfo])
321
+
225
322
  return imageProps
226
323
  }
227
324
 
228
- function isHorizontal(val: PositionVal): val is 'left' | 'right' {
325
+ function isHorizontal (val: PositionVal): val is 'left' | 'right' {
229
326
  return typeof val === 'string' && /^(left|right)$/.test(val)
230
327
  }
231
328
 
232
- function isVertical(val: PositionVal): val is 'top' | 'bottom' {
329
+ function isVertical (val: PositionVal): val is 'top' | 'bottom' {
233
330
  return typeof val === 'string' && /^(top|bottom)$/.test(val)
234
331
  }
235
332
 
236
- function normalizeBackgroundPosition(parts: PositionVal[]): backgroundPositionList {
237
-
333
+ function normalizeBackgroundPosition (parts: PositionVal[]): backgroundPositionList {
238
334
  if (parts.length === 0) return []
239
335
 
240
336
  // 定义默认值
@@ -299,71 +395,184 @@ function normalizeBackgroundPosition(parts: PositionVal[]): backgroundPositionLi
299
395
  return [hStart, hOffset, vStart, vOffset] as backgroundPositionList
300
396
  }
301
397
 
302
- function preParseImage(imageStyle?: ExtendedViewStyle) {
398
+ /**
399
+ *
400
+ * calcSteps - 计算起始位置和终点位置之间的差值
401
+ * startVal - 起始位置距离
402
+ * endVal - 终点位置距离
403
+ * len - 数量
404
+ * **/
405
+ function calcSteps (startVal: number, endVal: number, len: number) {
406
+ const diffVal = endVal - startVal
407
+ const step = diffVal / len
408
+ const newArr: Array<number> = []
409
+ for (let i = 1; i < len; i++) {
410
+ const val = startVal + step * i
411
+ newArr.push(+val.toFixed(2))
412
+ }
413
+
414
+ return newArr
415
+ }
416
+
417
+ function parseLinearGradient (text: string): LinearInfo | undefined {
418
+ let linearText = text.trim().match(/linear-gradient\((.*)\)/)?.[1]
419
+ if (!linearText) return
420
+
421
+ // 添加默认的角度
422
+ if (!/^to|^-?\d+deg/.test(linearText)) {
423
+ linearText = '180deg ,' + linearText
424
+ } else {
425
+ linearText = linearText.replace('to', '')
426
+ }
427
+
428
+ // 把 0deg, red 10%, blue 20% 解析为 ['0deg', 'red, 10%', 'blue, 20%']
429
+ const [direction, ...colorList] = linearText.split(/,(?![^(#]*\))/)
430
+ // 记录需要填充起点的起始位置
431
+ let startIdx = 0; let startVal = 0
432
+ // 把 ['red, 10%', 'blue, 20%']解析为 [[red, 10%], [blue, 20%]]
433
+ const linearInfo = colorList.map(item => item.trim().split(/(?<!,)\s+/))
434
+ .reduce<LinearInfo>((prev, cur, idx, self) => {
435
+ const { colors, locations } = prev
436
+ const [color, val] = cur
437
+ let numberVal: number = parseFloat(val) / 100
438
+
439
+ // 处理渐变默认值
440
+ if (idx === 0) {
441
+ numberVal = numberVal || 0
442
+ } else if (self.length - 1 === idx) {
443
+ numberVal = numberVal || 1
444
+ }
445
+
446
+ // 出现缺省值时进行填充
447
+ if (idx - startIdx > 1 && !isNaN(numberVal)) {
448
+ locations.push(...calcSteps(startVal, numberVal, idx - startIdx))
449
+ }
450
+
451
+ if (!isNaN(numberVal)) {
452
+ startIdx = idx
453
+ startVal = numberVal
454
+ }
303
455
 
304
- const { backgroundImage, backgroundSize = ['auto'], backgroundPosition = [0, 0] } = imageStyle || {}
305
- const src = parseUrl(backgroundImage)
456
+ // 添加color的数组
457
+ colors.push(color.trim())
306
458
 
307
- let sizeList = backgroundSize.slice() as DimensionValue[]
459
+ !isNaN(numberVal) && locations.push(numberVal)
460
+ return prev
461
+ }, { colors: [], locations: [] })
462
+
463
+ return {
464
+ ...linearInfo,
465
+ direction: direction.trim()
466
+ }
467
+ }
468
+
469
+ function parseBgImage (text: string): {
470
+ linearInfo?: LinearInfo;
471
+ direction?: string;
472
+ type?: 'image' | 'linear'
473
+ src?: string
474
+ } {
475
+ if (!text) return {}
308
476
 
309
- sizeList.length === 1 && sizeList.push('auto')
477
+ const src = parseUrl(text)
478
+ if (src) return { src, type: 'image' }
479
+
480
+ const linearInfo = parseLinearGradient(text)
481
+ if (!linearInfo) return {}
482
+ return {
483
+ linearInfo,
484
+ type: 'linear'
485
+ }
486
+ }
487
+
488
+ function normalizeBackgroundSize (backgroundSize: Exclude<ExtendedViewStyle['backgroundSize'], undefined>, type: 'image' | 'linear' | undefined) {
489
+ const sizeList = backgroundSize.slice()
490
+ if (sizeList.length === 1) sizeList.push('auto')
491
+
492
+ if (type === 'linear') {
493
+ // 处理当使用渐变的时候,background-size出现cover, contain, auto,当作100%处理
494
+ for (const i in sizeList) {
495
+ const val = sizeList[i]
496
+ sizeList[i] = /^cover|contain|auto$/.test(val as string) ? '100%' : val
497
+ }
498
+ }
499
+
500
+ return sizeList
501
+ }
502
+
503
+ function preParseImage (imageStyle?: ExtendedViewStyle) {
504
+ const { backgroundImage = '', backgroundSize = ['auto'], backgroundPosition = [0, 0] } = imageStyle || {}
505
+ const { type, src, linearInfo } = parseBgImage(backgroundImage)
310
506
 
311
507
  return {
312
508
  src,
313
- sizeList,
509
+ linearInfo,
510
+ type,
511
+ sizeList: normalizeBackgroundSize(backgroundSize, type),
314
512
  backgroundPosition: normalizeBackgroundPosition(backgroundPosition)
315
513
  }
316
514
  }
317
515
 
318
- function wrapImage(imageStyle?: ExtendedViewStyle) {
319
- const [show, setShow] = useState<boolean>(false)
320
- const [, setImageSizeWidth] = useState<number | null>(null)
321
- const [, setImageSizeHeight] = useState<number | null>(null)
322
- const [, setLayoutInfoWidth] = useState<number | null>(null)
323
- const [, setLayoutInfoHeight] = useState<number | null>(null)
324
- const sizeInfo = useRef<Size | null>(null)
325
- const layoutInfo = useRef<Size | null>(null)
516
+ function isDiagonalAngle (linearInfo?: LinearInfo): boolean {
517
+ return !!(linearInfo?.direction && diagonalAngleMap[linearInfo.direction])
518
+ }
326
519
 
327
- // 预解析
520
+ function wrapImage (imageStyle?: ExtendedViewStyle) {
521
+ // 预处理数据
328
522
  const preImageInfo: PreImageInfo = preParseImage(imageStyle)
523
+ // 预解析
524
+ const { src, sizeList, type } = preImageInfo
329
525
 
330
526
  // 判断是否可挂载onLayout
331
527
  const { needLayout, needImageSize } = checkNeedLayout(preImageInfo)
332
- const { src } = preImageInfo
333
528
 
529
+ const [show, setShow] = useState<boolean>(((type === 'image' && !!src) || type === 'linear') && !needLayout && !needImageSize)
530
+ const [, setImageSizeWidth] = useState<number | null>(null)
531
+ const [, setImageSizeHeight] = useState<number | null>(null)
532
+ const [, setLayoutInfoWidth] = useState<number | null>(null)
533
+ const [, setLayoutInfoHeight] = useState<number | null>(null)
534
+ const sizeInfo = useRef<Size | null>(null)
535
+ const layoutInfo = useRef<Size | null>(null)
334
536
  useEffect(() => {
335
- if(!src) {
336
- setShow(false)
337
- sizeInfo.current = null
338
- layoutInfo.current = null
339
- return
537
+ sizeInfo.current = null
538
+ if (type === 'linear') {
539
+ if (!needLayout) setShow(true)
540
+ return
340
541
  }
341
542
 
342
- if (!needImageSize) {
543
+ if (!src) {
544
+ setShow(false)
545
+ return
546
+ // 一开始未出现,数据改变时出现
547
+ } else if (!(needLayout || needImageSize)) {
343
548
  setShow(true)
344
549
  return
345
550
  }
346
- Image.getSize(src, (width, height) => {
347
- sizeInfo.current = {
348
- width,
349
- height
350
- }
351
- //1. 当需要绑定onLayout 2. 获取到布局信息
352
- if (!needLayout || layoutInfo.current) {
353
- setImageSizeWidth(width)
354
- setImageSizeHeight(height)
355
- if(layoutInfo.current) {
356
- setLayoutInfoWidth(layoutInfo.current.width)
357
- setLayoutInfoHeight(layoutInfo.current.height)
551
+
552
+ if (needImageSize) {
553
+ Image.getSize(src, (width, height) => {
554
+ sizeInfo.current = {
555
+ width,
556
+ height
358
557
  }
359
- setShow(true)
360
- }
361
- })
362
- }, [preImageInfo?.src])
558
+ // 1. 当需要绑定onLayout 2. 获取到布局信息
559
+ if (!needLayout || layoutInfo.current) {
560
+ setImageSizeWidth(width)
561
+ setImageSizeHeight(height)
562
+ if (layoutInfo.current) {
563
+ setLayoutInfoWidth(layoutInfo.current.width)
564
+ setLayoutInfoHeight(layoutInfo.current.height)
565
+ }
566
+ setShow(true)
567
+ }
568
+ })
569
+ }
570
+ // type 添加type 处理无渐变 有渐变的场景
571
+ }, [src, type])
363
572
 
364
- if (!preImageInfo?.src) return null
573
+ if (!type) return null
365
574
 
366
- const onLayout = (res: LayoutChangeEvent ) => {
575
+ const onLayout = (res: LayoutChangeEvent) => {
367
576
  const { width, height } = res?.nativeEvent?.layout || {}
368
577
  layoutInfo.current = {
369
578
  width,
@@ -372,90 +581,75 @@ function wrapImage(imageStyle?: ExtendedViewStyle) {
372
581
  if (!needImageSize) {
373
582
  setLayoutInfoWidth(width)
374
583
  setLayoutInfoHeight(height)
584
+ // 有渐变角度的时候,才触发渲染组件
585
+ if (type === 'linear') {
586
+ sizeInfo.current = {
587
+ width: calcPercent(sizeList[0] as NumberVal, width),
588
+ height: calcPercent(sizeList[1] as NumberVal, height)
589
+ }
590
+ setImageSizeWidth(sizeInfo.current.width)
591
+ setImageSizeHeight(sizeInfo.current.height)
592
+ setShow(true)
593
+ }
375
594
  } else if (sizeInfo.current) {
376
595
  setLayoutInfoWidth(width)
377
596
  setLayoutInfoHeight(height)
378
597
  setImageSizeWidth(sizeInfo.current.width)
379
- setImageSizeHeight(sizeInfo.current.height)
598
+ setImageSizeHeight(sizeInfo.current.height)
380
599
  setShow(true)
381
600
  }
382
601
  }
383
602
 
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)} />}
603
+ return <View key='backgroundImage' {...needLayout ? { onLayout } : null} style={{ ...StyleSheet.absoluteFillObject, width: '100%', height: '100%', overflow: 'hidden' }}>
604
+ {show && type === 'linear' && <LinearGradient useAngle={true} {...imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size)} /> }
605
+ {show && type === 'image' && <Image {...imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size)} />}
386
606
  </View>
387
607
  }
388
608
 
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
609
+ interface WrapChildrenConfig {
610
+ hasVarDec: boolean
611
+ enableBackground: boolean
612
+ textStyle?: TextStyle
613
+ backgroundStyle?: ExtendedViewStyle
614
+ varContext?: Record<string, any>
615
+ textProps?: Record<string, any>
616
+ }
392
617
 
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
- }
398
- } else {
399
- if (textStyle) throwReactWarning('[Mpx runtime warn]: Text style will be ignored unless every child of the view is Text node!')
400
- }
618
+ function wrapWithChildren (props: _ViewProps, { hasVarDec, enableBackground, textStyle, backgroundStyle, varContext, textProps }: WrapChildrenConfig) {
619
+ const children = wrapChildren(props, {
620
+ hasVarDec,
621
+ varContext,
622
+ textStyle,
623
+ textProps
624
+ })
401
625
 
402
626
  return [
403
- enableBackgroundImage ? wrapImage(imageStyle) : null,
627
+ enableBackground ? wrapImage(backgroundStyle) : null,
404
628
  children
405
629
  ]
406
630
  }
407
631
 
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
- }]
436
- const {
632
+ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, ref): JSX.Element => {
633
+ const { textProps, innerProps: props = {} } = splitProps(viewProps)
634
+ let {
437
635
  style = {},
438
- children,
439
- hoverStyle,
636
+ 'hover-style': hoverStyle,
440
637
  'hover-start-time': hoverStartTime = 50,
441
638
  'hover-stay-time': hoverStayTime = 400,
442
- 'enable-offset': enableOffset,
639
+ 'enable-var': enableVar,
640
+ 'external-var-context': externalVarContext,
641
+ 'enable-background': enableBackground,
642
+ 'parent-font-size': parentFontSize,
643
+ 'parent-width': parentWidth,
644
+ 'parent-height': parentHeight
443
645
  } = props
444
646
 
445
647
  const [isHover, setIsHover] = useState(false)
446
- let transformStyle = {}
447
-
448
- const [containerWidth, setContainerWidth] = useState(0)
449
- const [containerHeight, setContainerHeight] = useState(0)
450
-
451
- const layoutRef = useRef({})
452
648
 
453
- // 打平 style 数组
454
- const styleObj: ExtendedViewStyle = normalizeStyle(style)
455
649
  // 默认样式
456
650
  const defaultStyle: ExtendedViewStyle = {
457
651
  // flex 布局相关的默认样式
458
- ...styleObj.display === 'flex' && {
652
+ ...style.display === 'flex' && {
459
653
  flexDirection: 'row',
460
654
  flexBasis: 'auto',
461
655
  flexShrink: 1,
@@ -463,22 +657,33 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
463
657
  }
464
658
  }
465
659
 
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
- })
660
+ const styleObj: ExtendedViewStyle = {
661
+ ...defaultStyle,
662
+ ...style,
663
+ ...(isHover ? hoverStyle : null)
664
+ }
665
+
666
+ const {
667
+ normalStyle,
668
+ hasSelfPercent,
669
+ hasVarDec,
670
+ varContextRef,
671
+ setWidth,
672
+ setHeight
673
+ } = useTransformStyle(styleObj, {
674
+ enableVar,
675
+ externalVarContext,
676
+ parentFontSize,
677
+ parentWidth,
678
+ parentHeight
478
679
  })
479
680
 
480
- if (hasPercentStyle) {
481
- transformStyle = percentTransform(combinationStyleProps, { width: containerWidth, height: containerHeight })
681
+ const { textStyle, backgroundStyle, innerStyle } = splitStyle(normalStyle)
682
+
683
+ enableBackground = enableBackground || !!backgroundStyle
684
+ const enableBackgroundRef = useRef(enableBackground)
685
+ if (enableBackgroundRef.current !== enableBackground) {
686
+ throw new Error('[Mpx runtime error]: background use should be stable in the component lifecycle, or you can set [enable-background] with true.')
482
687
  }
483
688
 
484
689
  const { nodeRef } = useNodesRef<View, _ViewProps>(props, ref, {
@@ -512,104 +717,37 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
512
717
  }, +hoverStayTime)
513
718
  }
514
719
 
515
- function onTouchStart(e: NativeSyntheticEvent<TouchEvent>) {
516
- const { bindtouchstart } = props;
720
+ function onTouchStart (e: NativeSyntheticEvent<TouchEvent>) {
721
+ const { bindtouchstart } = props
517
722
  bindtouchstart && bindtouchstart(e)
518
723
  setStartTimer()
519
724
  }
520
725
 
521
- function onTouchEnd(e: NativeSyntheticEvent<TouchEvent>) {
522
- const { bindtouchend } = props;
726
+ function onTouchEnd (e: NativeSyntheticEvent<TouchEvent>) {
727
+ const { bindtouchend } = props
523
728
  bindtouchend && bindtouchend(e)
524
729
  setStayTimer()
525
730
  }
526
731
 
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
- }
574
- })
575
- return styleMap
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
- })
594
-
595
- const needLayout = enableOffset || hasPercentStyle
732
+ const {
733
+ layoutRef,
734
+ layoutStyle,
735
+ layoutProps
736
+ } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef })
596
737
 
597
738
  const innerProps = useInnerProps(props, {
598
739
  ref: nodeRef,
599
- ...needLayout ? { onLayout } : {},
740
+ style: { ...innerStyle, ...layoutStyle },
741
+ ...layoutProps,
600
742
  ...(hoverStyle && {
601
743
  bindtouchstart: onTouchStart,
602
744
  bindtouchend: onTouchEnd
603
745
  })
604
746
  }, [
605
- 'style',
606
- 'children',
607
747
  'hover-start-time',
608
748
  'hover-stay-time',
609
- 'hoverStyle',
610
- 'hover-class',
611
- 'enable-offset',
612
- 'enable-background-image'
749
+ 'hover-style',
750
+ 'hover-class'
613
751
  ], {
614
752
  layoutRef
615
753
  })
@@ -617,9 +755,20 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
617
755
  return (
618
756
  <View
619
757
  {...innerProps}
620
- style={{ ...innerStyle, ...transformStyle }}
621
758
  >
622
- {wrapChildren(children, props, textStyle, imageStyle)}
759
+ {
760
+ wrapWithChildren(
761
+ props,
762
+ {
763
+ hasVarDec,
764
+ enableBackground: enableBackgroundRef.current,
765
+ textStyle,
766
+ backgroundStyle,
767
+ varContext: varContextRef.current,
768
+ textProps
769
+ }
770
+ )
771
+ }
623
772
  </View>
624
773
  )
625
774
  })
@@ -627,4 +776,3 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((props, ref):
627
776
  _View.displayName = 'mpx-view'
628
777
 
629
778
  export default _View
630
-