@mpxjs/webpack-plugin 2.9.62 → 2.9.65

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