@mpxjs/webpack-plugin 2.10.24 → 2.11.0

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 (151) hide show
  1. package/lib/index.js +4 -1
  2. package/lib/platform/style/wx/index.js +417 -120
  3. package/lib/platform/template/wx/component-config/picker.js +0 -6
  4. package/lib/react/style-helper.js +6 -6
  5. package/lib/resolver/AddEnvPlugin.js +1 -1
  6. package/lib/resolver/AddModePlugin.js +1 -1
  7. package/lib/resolver/ExtendComponentsPlugin.js +68 -0
  8. package/lib/runtime/components/extends/section-list.mpx +14 -0
  9. package/lib/runtime/components/react/animationHooks/index.ts +7 -3
  10. package/lib/runtime/components/react/dist/animationHooks/index.d.ts +0 -2
  11. package/lib/runtime/components/react/dist/animationHooks/index.js +6 -1
  12. package/lib/runtime/components/react/dist/event.config.d.ts +2 -6
  13. package/lib/runtime/components/react/dist/event.config.js +28 -24
  14. package/lib/runtime/components/react/dist/getInnerListeners.d.ts +1 -1
  15. package/lib/runtime/components/react/dist/getInnerListeners.js +74 -56
  16. package/lib/runtime/components/react/dist/mpx-button.d.ts +1 -2
  17. package/lib/runtime/components/react/dist/mpx-button.jsx +16 -9
  18. package/lib/runtime/components/react/dist/mpx-camera.d.ts +0 -2
  19. package/lib/runtime/components/react/dist/mpx-camera.jsx +36 -5
  20. package/lib/runtime/components/react/dist/mpx-canvas/index.d.ts +0 -2
  21. package/lib/runtime/components/react/dist/mpx-canvas/index.jsx +1 -3
  22. package/lib/runtime/components/react/dist/mpx-canvas/utils.d.ts +1 -1
  23. package/lib/runtime/components/react/dist/mpx-checkbox-group.d.ts +0 -2
  24. package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +3 -4
  25. package/lib/runtime/components/react/dist/mpx-checkbox.d.ts +1 -2
  26. package/lib/runtime/components/react/dist/mpx-checkbox.jsx +8 -9
  27. package/lib/runtime/components/react/dist/mpx-form.d.ts +1 -2
  28. package/lib/runtime/components/react/dist/mpx-form.jsx +5 -5
  29. package/lib/runtime/components/react/dist/mpx-icon/index.d.ts +0 -2
  30. package/lib/runtime/components/react/dist/mpx-icon/index.jsx +2 -2
  31. package/lib/runtime/components/react/dist/mpx-image.d.ts +0 -2
  32. package/lib/runtime/components/react/dist/mpx-image.jsx +59 -43
  33. package/lib/runtime/components/react/dist/mpx-inline-text.jsx +13 -4
  34. package/lib/runtime/components/react/dist/mpx-input.d.ts +0 -2
  35. package/lib/runtime/components/react/dist/mpx-input.jsx +16 -8
  36. package/lib/runtime/components/react/dist/mpx-label.d.ts +1 -2
  37. package/lib/runtime/components/react/dist/mpx-label.jsx +5 -6
  38. package/lib/runtime/components/react/dist/mpx-movable-area.d.ts +0 -2
  39. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +3 -3
  40. package/lib/runtime/components/react/dist/mpx-movable-view.d.ts +1 -2
  41. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +6 -6
  42. package/lib/runtime/components/react/dist/mpx-nav.jsx +1 -1
  43. package/lib/runtime/components/react/dist/mpx-picker/index.jsx +72 -23
  44. package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +2 -2
  45. package/lib/runtime/components/react/dist/mpx-picker/type.d.ts +4 -0
  46. package/lib/runtime/components/react/dist/mpx-picker-view/index.d.ts +1 -1
  47. package/lib/runtime/components/react/dist/mpx-picker-view/index.jsx +10 -14
  48. package/lib/runtime/components/react/dist/mpx-picker-view/pickerVIewContext.d.ts +0 -2
  49. package/lib/runtime/components/react/dist/mpx-picker-view/pickerVIewContext.js +0 -5
  50. package/lib/runtime/components/react/dist/mpx-picker-view-column/index.d.ts +1 -1
  51. package/lib/runtime/components/react/dist/mpx-picker-view-column/index.jsx +34 -14
  52. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItem.d.ts +0 -2
  53. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItem.jsx +4 -5
  54. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItemLite.d.ts +0 -2
  55. package/lib/runtime/components/react/dist/mpx-picker-view-column/pickerViewColumnItemLite.jsx +3 -5
  56. package/lib/runtime/components/react/dist/mpx-popup/index.d.ts +5 -4
  57. package/lib/runtime/components/react/dist/mpx-popup/index.jsx +2 -2
  58. package/lib/runtime/components/react/dist/mpx-popup/popupBase.jsx +3 -2
  59. package/lib/runtime/components/react/dist/mpx-progress.d.ts +0 -2
  60. package/lib/runtime/components/react/dist/mpx-progress.jsx +1 -3
  61. package/lib/runtime/components/react/dist/mpx-radio-group.d.ts +0 -2
  62. package/lib/runtime/components/react/dist/mpx-radio-group.jsx +3 -4
  63. package/lib/runtime/components/react/dist/mpx-radio.d.ts +1 -2
  64. package/lib/runtime/components/react/dist/mpx-radio.jsx +8 -9
  65. package/lib/runtime/components/react/dist/mpx-rich-text/index.d.ts +0 -2
  66. package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +1 -3
  67. package/lib/runtime/components/react/dist/mpx-scroll-view.d.ts +1 -2
  68. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +6 -6
  69. package/lib/runtime/components/react/dist/mpx-section-list.d.ts +51 -0
  70. package/lib/runtime/components/react/dist/mpx-section-list.jsx +376 -0
  71. package/lib/runtime/components/react/dist/mpx-simple-text.jsx +37 -19
  72. package/lib/runtime/components/react/dist/mpx-simple-view.d.ts +4 -1
  73. package/lib/runtime/components/react/dist/mpx-simple-view.jsx +11 -4
  74. package/lib/runtime/components/react/dist/mpx-slider.d.ts +0 -2
  75. package/lib/runtime/components/react/dist/mpx-slider.jsx +6 -7
  76. package/lib/runtime/components/react/dist/mpx-sticky-header.d.ts +1 -2
  77. package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +5 -5
  78. package/lib/runtime/components/react/dist/mpx-sticky-section.d.ts +1 -2
  79. package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +6 -6
  80. package/lib/runtime/components/react/dist/mpx-swiper-item.d.ts +1 -2
  81. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +5 -5
  82. package/lib/runtime/components/react/dist/mpx-swiper.d.ts +1 -2
  83. package/lib/runtime/components/react/dist/mpx-swiper.jsx +4 -8
  84. package/lib/runtime/components/react/dist/mpx-switch.d.ts +0 -2
  85. package/lib/runtime/components/react/dist/mpx-switch.jsx +1 -3
  86. package/lib/runtime/components/react/dist/mpx-text.d.ts +0 -2
  87. package/lib/runtime/components/react/dist/mpx-text.jsx +43 -25
  88. package/lib/runtime/components/react/dist/mpx-video.d.ts +0 -2
  89. package/lib/runtime/components/react/dist/mpx-video.jsx +4 -5
  90. package/lib/runtime/components/react/dist/mpx-view.d.ts +2 -2
  91. package/lib/runtime/components/react/dist/mpx-view.jsx +214 -114
  92. package/lib/runtime/components/react/dist/mpx-web-view.d.ts +2 -1
  93. package/lib/runtime/components/react/dist/mpx-web-view.jsx +5 -1
  94. package/lib/runtime/components/react/dist/namedColorSet.d.ts +2 -0
  95. package/lib/runtime/components/react/dist/namedColorSet.js +149 -0
  96. package/lib/runtime/components/react/dist/utils.d.ts +71 -70
  97. package/lib/runtime/components/react/dist/utils.jsx +1295 -456
  98. package/lib/runtime/components/react/event.config.ts +32 -25
  99. package/lib/runtime/components/react/getInnerListeners.ts +84 -60
  100. package/lib/runtime/components/react/mpx-button.tsx +18 -18
  101. package/lib/runtime/components/react/mpx-camera.tsx +40 -8
  102. package/lib/runtime/components/react/mpx-canvas/index.tsx +5 -7
  103. package/lib/runtime/components/react/mpx-canvas/utils.tsx +3 -1
  104. package/lib/runtime/components/react/mpx-checkbox-group.tsx +2 -8
  105. package/lib/runtime/components/react/mpx-checkbox.tsx +9 -13
  106. package/lib/runtime/components/react/mpx-form.tsx +6 -8
  107. package/lib/runtime/components/react/mpx-icon/index.tsx +1 -5
  108. package/lib/runtime/components/react/mpx-image.tsx +66 -58
  109. package/lib/runtime/components/react/mpx-inline-text.tsx +13 -4
  110. package/lib/runtime/components/react/mpx-input.tsx +19 -12
  111. package/lib/runtime/components/react/mpx-label.tsx +6 -10
  112. package/lib/runtime/components/react/mpx-movable-area.tsx +3 -5
  113. package/lib/runtime/components/react/mpx-movable-view.tsx +7 -9
  114. package/lib/runtime/components/react/mpx-nav.tsx +1 -1
  115. package/lib/runtime/components/react/mpx-picker/index.tsx +100 -29
  116. package/lib/runtime/components/react/mpx-picker/multiSelector.tsx +3 -3
  117. package/lib/runtime/components/react/mpx-picker/selector.tsx +1 -1
  118. package/lib/runtime/components/react/mpx-picker/type.ts +4 -0
  119. package/lib/runtime/components/react/mpx-picker-view/index.tsx +17 -24
  120. package/lib/runtime/components/react/mpx-picker-view/pickerVIewContext.ts +0 -9
  121. package/lib/runtime/components/react/mpx-picker-view-column/index.tsx +44 -23
  122. package/lib/runtime/components/react/mpx-picker-view-column/pickerViewColumnItem.tsx +1 -9
  123. package/lib/runtime/components/react/mpx-picker-view-column/pickerViewColumnItemLite.tsx +0 -9
  124. package/lib/runtime/components/react/mpx-popup/index.tsx +7 -3
  125. package/lib/runtime/components/react/mpx-popup/popupBase.tsx +3 -2
  126. package/lib/runtime/components/react/mpx-progress.tsx +0 -6
  127. package/lib/runtime/components/react/mpx-radio-group.tsx +2 -8
  128. package/lib/runtime/components/react/mpx-radio.tsx +9 -13
  129. package/lib/runtime/components/react/mpx-rich-text/index.tsx +0 -6
  130. package/lib/runtime/components/react/mpx-scroll-view.tsx +7 -9
  131. package/lib/runtime/components/react/mpx-section-list.tsx +557 -0
  132. package/lib/runtime/components/react/mpx-simple-text.tsx +36 -25
  133. package/lib/runtime/components/react/mpx-simple-view.tsx +17 -5
  134. package/lib/runtime/components/react/mpx-slider.tsx +15 -13
  135. package/lib/runtime/components/react/mpx-sticky-header.tsx +6 -8
  136. package/lib/runtime/components/react/mpx-sticky-section.tsx +7 -9
  137. package/lib/runtime/components/react/mpx-swiper-item.tsx +6 -7
  138. package/lib/runtime/components/react/mpx-swiper.tsx +5 -11
  139. package/lib/runtime/components/react/mpx-switch.tsx +0 -6
  140. package/lib/runtime/components/react/mpx-text.tsx +45 -36
  141. package/lib/runtime/components/react/mpx-video.tsx +3 -8
  142. package/lib/runtime/components/react/mpx-view.tsx +235 -129
  143. package/lib/runtime/components/react/mpx-web-view.tsx +12 -6
  144. package/lib/runtime/components/react/namedColorSet.ts +150 -0
  145. package/lib/runtime/components/react/types/getInnerListeners.d.ts +21 -1
  146. package/lib/runtime/components/react/types/global.d.ts +1 -0
  147. package/lib/runtime/components/react/utils.tsx +1352 -550
  148. package/lib/runtime/i18n.wxs +15 -9
  149. package/lib/style-compiler/strip-conditional.js +19 -4
  150. package/lib/template-compiler/compiler.js +6 -0
  151. package/package.json +6 -6
@@ -4,16 +4,17 @@
4
4
  * ✔ hover-start-time
5
5
  * ✔ hover-stay-time
6
6
  */
7
- import { View, TextStyle, NativeSyntheticEvent, ViewProps, ImageStyle, StyleSheet, Image, LayoutChangeEvent } from 'react-native'
8
- import { useRef, useState, useEffect, forwardRef, ReactNode, JSX, createElement } from 'react'
7
+ import { View, NativeSyntheticEvent, ViewProps, ImageStyle, StyleSheet, Image, LayoutChangeEvent } from 'react-native'
8
+ import { useRef, useState, useEffect, useMemo, forwardRef, ReactNode, JSX, createElement } from 'react'
9
9
  import useInnerProps from './getInnerListeners'
10
10
  import Animated from 'react-native-reanimated'
11
11
  import useAnimationHooks, { AnimationType } from './animationHooks/index'
12
12
  import type { AnimationProp } from './animationHooks/utils'
13
13
  import { ExtendedViewStyle } from './types/common'
14
14
  import useNodesRef, { HandlerRef } from './useNodesRef'
15
- import { parseUrl, PERCENT_REGEX, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout, renderImage, pickStyle, extendObject, useHover, useTextPassThroughValue } from './utils'
16
- import { error, isFunction } from '@mpxjs/utils'
15
+ import { parseUrl, percentRegExp, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout, renderImage, pickStyle, extendObject, useHover, useTextPassThrough } from './utils'
16
+ import { TextPassThroughContextValue } from './context'
17
+ import { error, warn, hasOwn } from '@mpxjs/utils'
17
18
  import * as perf from '@mpxjs/perf'
18
19
  import LinearGradient from 'react-native-linear-gradient'
19
20
  import { GestureDetector, PanGesture } from 'react-native-gesture-handler'
@@ -27,10 +28,9 @@ export interface _ViewProps extends ViewProps {
27
28
  'hover-start-time'?: number
28
29
  'hover-stay-time'?: number
29
30
  'enable-background'?: boolean
31
+ 'enable-text-pass-through'?: boolean
30
32
  'enable-var'?: boolean
31
33
  'enable-fast-image'?: boolean
32
- 'external-var-context'?: Record<string, any>
33
- 'parent-font-size'?: number
34
34
  'parent-width'?: number
35
35
  'parent-height'?: number
36
36
  'enable-animation'?: boolean | AnimationType
@@ -41,8 +41,6 @@ export interface _ViewProps extends ViewProps {
41
41
  catchtransitionend?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void
42
42
  }
43
43
 
44
- type Handler = (...args: any[]) => void
45
-
46
44
  type Size = {
47
45
  width: number
48
46
  height: number
@@ -84,12 +82,16 @@ type ImageProps = {
84
82
  style: ImageStyle,
85
83
  src?: string,
86
84
  source?: { uri: string },
87
- colors: Array<string>,
85
+ colors?: Array<string>,
88
86
  locations?: Array<number>
89
87
  angle?: number
90
88
  resizeMode?: 'cover' | 'stretch'
91
89
  }
92
90
 
91
+ type LinearImageProps = ImageProps & {
92
+ colors: Array<string>
93
+ }
94
+
93
95
  const linearMap = new Map([
94
96
  ['top', 0],
95
97
  ['bottom', 180],
@@ -97,6 +99,19 @@ const linearMap = new Map([
97
99
  ['right', 90]
98
100
  ])
99
101
 
102
+ const FLEX_DEFAULT_STYLE: ExtendedViewStyle = {
103
+ flexDirection: 'row',
104
+ flexBasis: 'auto',
105
+ flexShrink: 1,
106
+ flexWrap: 'nowrap'
107
+ }
108
+ // 用户传入 flex shorthand 时使用的精简版(裁掉 flexBasis/flexShrink)
109
+ // 避免 number 形式 flex:1 被 default flexBasis:'auto' 反向覆盖
110
+ const FLEX_DEFAULT_STYLE_TRIMMED: ExtendedViewStyle = {
111
+ flexDirection: 'row',
112
+ flexWrap: 'nowrap'
113
+ }
114
+
100
115
  // 对角线角度
101
116
  const diagonalAngleMap: Record<string, (width: number, height: number) => any> = {
102
117
  'top right': (width: number, height: number) => {
@@ -122,24 +137,36 @@ function radToAngle (r: number) {
122
137
  return r * 180 / Math.PI
123
138
  }
124
139
 
125
- const applyHandlers = (handlers: Handler[], args: any[]) => {
126
- for (const handler of handlers) {
127
- handler(...args)
140
+ // === linear-gradient 解析相关正则(模块级,一次创建) ===
141
+ // CSS <angle> 支持 deg/turn/rad/grad 四种单位,统一归一为 deg
142
+ // https://www.w3.org/TR/css-values-3/#angles
143
+ const angleRegExp = /^\s*(-?\d+(?:\.\d+)?)(deg|turn|rad|grad)\b/
144
+ const gradientToRegExp = /^to\b\s*/
145
+ // 入口形态:仅在含 linear-gradient 时走解析;提取括号内内容
146
+ const linearGradientRegExp = /linear-gradient\((.*)\)/
147
+ // 顶层逗号切分(忽略 () / # 内部的逗号)
148
+ const topLevelCommaRegExp = /,(?![^(#]*\))/
149
+ // 色标内空白切分(忽略色函数里的逗号边界)
150
+ const stopWhitespaceRegExp = /(?<!,)\s+/
151
+ // 色标位置仅支持 `<n>%` 或裸 `0`(CSS 允许 0 作为唯一无单位 length)
152
+ // 其它单位(px/rpx/em/vw/...)当前无法在不依赖 layout 的情况下归一为百分比,统一拒绝
153
+ const validStopPosRegExp = /^-?\d+(?:\.\d+)?%$|^0$/
154
+ // color hint 检测:色标 token 仅含一个分量且以数字/正负号/小数点开头
155
+ const colorHintLeadingRegExp = /^[-+.\d]/
156
+
157
+ function normalizeAngle (raw: string): number | undefined {
158
+ const m = raw.match(angleRegExp)
159
+ if (!m) return undefined
160
+ const n = +m[1]
161
+ switch (m[2]) {
162
+ case 'turn': return n * 360
163
+ case 'rad': return radToAngle(n)
164
+ case 'grad': return n * 0.9
165
+ default: return n // deg
128
166
  }
129
167
  }
130
168
 
131
- const normalizeStyle = (style: ExtendedViewStyle = {}) => {
132
- ['backgroundSize', 'backgroundPosition'].forEach(name => {
133
- if (style[name] && typeof style[name] === 'string') {
134
- if (style[name].trim()) {
135
- style[name] = style[name].split(' ')
136
- }
137
- }
138
- })
139
- return style
140
- }
141
-
142
- const isPercent = (val: string | number | undefined): val is string => typeof val === 'string' && PERCENT_REGEX.test(val)
169
+ const isPercent = (val: string | number | undefined): val is string => typeof val === 'string' && percentRegExp.test(val)
143
170
 
144
171
  const isBackgroundSizeKeyword = (val: string | number): boolean => typeof val === 'string' && /^cover|contain$/.test(val)
145
172
 
@@ -261,12 +288,12 @@ function backgroundSize (imageProps: ImageProps, preImageInfo: PreImageInfo, ima
261
288
  // 枚举值
262
289
  if (typeof width === 'string' && ['cover', 'contain'].includes(width)) {
263
290
  if (layoutInfo && imageSize) {
264
- const layoutRatio = layoutWidth / imageSizeWidth
265
- const eleRatio = imageSizeWidth / imageSizeHeight
266
- // 容器宽高比 大于 图片的宽高比,依据宽度作为基准,否则以高度为基准
267
- if ((layoutRatio <= eleRatio && (width as string) === 'contain') || (layoutRatio >= eleRatio && (width as string) === 'cover')) {
291
+ const containerRatio = layoutWidth / layoutHeight
292
+ const imageRatio = imageSizeWidth / imageSizeHeight
293
+ // 容器宽高比 小于等于 图片宽高比:contain 按宽缩放,cover 按高缩放
294
+ if ((containerRatio <= imageRatio && (width as string) === 'contain') || (containerRatio >= imageRatio && (width as string) === 'cover')) {
268
295
  dimensions = calculateSize(layoutWidth as number, imageSizeHeight / imageSizeWidth, true) as Size
269
- } else if ((layoutRatio > eleRatio && (width as string) === 'contain') || (layoutRatio < eleRatio && (width as string) === 'cover')) {
296
+ } else if ((containerRatio > imageRatio && (width as string) === 'contain') || (containerRatio < imageRatio && (width as string) === 'cover')) {
270
297
  dimensions = calculateSize(layoutHeight as number, imageSizeWidth / imageSizeHeight) as Size
271
298
  }
272
299
  }
@@ -325,7 +352,16 @@ function linearGradient (imageProps: ImageProps, preImageInfo: PreImageInfo, ima
325
352
  if (type !== 'linear') return
326
353
 
327
354
  // 角度计算
328
- let angle = +(linearMap.get(direction) || direction.match(/(-?\d+(\.\d+)?)deg/)?.[1] || 180) % 360
355
+ // 注意:不要用 `linearMap.get(direction) || ...`,因为 'top' 映射为 0 是 falsy 值,
356
+ // 会被误判为未命中并落到默认 180deg
357
+ let angle: number
358
+ if (linearMap.has(direction)) {
359
+ angle = linearMap.get(direction) as number
360
+ } else {
361
+ const normalized = normalizeAngle(direction)
362
+ angle = normalized !== undefined ? normalized : 180
363
+ }
364
+ angle = angle % 360
329
365
 
330
366
  // 对角线角度计算
331
367
  if (layoutInfo && diagonalAngleMap[direction] && imageSize && linearInfo) {
@@ -339,16 +375,29 @@ function linearGradient (imageProps: ImageProps, preImageInfo: PreImageInfo, ima
339
375
  }
340
376
 
341
377
  const imageStyleToProps = (preImageInfo: PreImageInfo, imageSize: Size, layoutInfo: Size) => {
342
- // 初始化
378
+ const { type } = preImageInfo
343
379
  const imageProps: ImageProps = {
344
- resizeMode: 'cover',
345
380
  style: {
346
381
  position: 'absolute'
347
382
  // ...StyleSheet.absoluteFillObject
348
- },
349
- colors: []
383
+ }
384
+ }
385
+
386
+ if (type === 'image') {
387
+ imageProps.resizeMode = 'cover'
388
+ } else {
389
+ imageProps.colors = []
390
+ }
391
+
392
+ backgroundSize(imageProps, preImageInfo, imageSize, layoutInfo)
393
+ if (preImageInfo.backgroundPosition.length) {
394
+ backgroundPosition(imageProps, preImageInfo, imageSize, layoutInfo)
395
+ }
396
+ if (type === 'image') {
397
+ backgroundImage(imageProps, preImageInfo)
398
+ } else {
399
+ linearGradient(imageProps, preImageInfo, imageSize, layoutInfo)
350
400
  }
351
- applyHandlers([backgroundSize, backgroundImage, backgroundPosition, linearGradient], [imageProps, preImageInfo, imageSize, layoutInfo])
352
401
 
353
402
  return imageProps
354
403
  }
@@ -456,23 +505,58 @@ function calcSteps (startVal: number, endVal: number, len: number) {
456
505
  return newArr
457
506
  }
458
507
 
459
- function parseLinearGradient (text: string): LinearInfo | undefined {
460
- let linearText = text.trim().match(/linear-gradient\((.*)\)/)?.[1]
508
+ // 返回值约定:
509
+ // - LinearInfo 解析成功
510
+ // - null 已识别为 linear-gradient 但内部已通过 error/warn 报错,外部不要再报
511
+ // - undefined 不是 linear-gradient 形态,外部按"不支持的 background-image"处理
512
+ function parseLinearGradient (text: string): LinearInfo | null | undefined {
513
+ let linearText = text.trim().match(linearGradientRegExp)?.[1]
461
514
  if (!linearText) return
462
515
 
463
- // 添加默认的角度
464
- if (!/^to|^-?\d+deg/.test(linearText)) {
465
- linearText = '180deg ,' + linearText
466
- } else {
467
- linearText = linearText.replace('to', '')
516
+ // 多重渐变 / 多重背景:linearGradientRegExp 用了贪婪 `.*`,多渐变时括号内
517
+ // 会贪到下一段 `linear-gradient(`,单段则一定不含;用 includes 一次判断即可
518
+ if (linearText.includes('linear-gradient(')) {
519
+ error(`[mpx-view] background-image 暂不支持多重渐变 (multi gradients),已丢弃,原值: ${text}`)
520
+ return null
468
521
  }
469
522
 
470
523
  // 把 0deg, red 10%, blue 20% 解析为 ['0deg', 'red, 10%', 'blue, 20%']
471
- const [direction, ...colorList] = linearText.split(/,(?![^(#]*\))/)
524
+ if (gradientToRegExp.test(linearText)) {
525
+ linearText = linearText.replace(gradientToRegExp, '')
526
+ } else if (!angleRegExp.test(linearText)) {
527
+ linearText = '180deg ,' + linearText
528
+ }
529
+ const [direction, ...colorList] = linearText.split(topLevelCommaRegExp)
472
530
  // 记录需要填充起点的起始位置
473
531
  let startIdx = 0; let startVal = 0
474
532
  // 把 ['red, 10%', 'blue, 20%']解析为 [[red, 10%], [blue, 20%]]
475
- const linearInfo = colorList.map(item => item.trim().split(/(?<!,)\s+/))
533
+ // 双位置语法展开:['red', '0%', '50%'] [['red', '0%'], ['red', '50%']]
534
+ // 等价于 CSS Images Level 4 中 `red 0% 50%` ≡ `red 0%, red 50%`
535
+ // 同步对不支持的写法做拦截:color hint(仅一个位置 token)、非百分比位置(px/rpx 等)
536
+ const linearInfo = colorList
537
+ .map(item => item.trim().split(stopWhitespaceRegExp))
538
+ .reduce<string[][]>((acc, parts) => {
539
+ // color hint: 例如 `linear-gradient(red, 30%, blue)` 中的 `30%`
540
+ // 该 token 仅含一个位置值、无颜色;当前实现无法做颜色采样,直接丢弃
541
+ if (parts.length === 1 && colorHintLeadingRegExp.test(parts[0])) {
542
+ warn(`[mpx-view] linear-gradient 暂不支持 color hint 写法 [${parts[0]}],已丢弃该色标`)
543
+ return acc
544
+ }
545
+ const [color, ...positions] = parts
546
+ if (positions.length === 0) {
547
+ acc.push([color])
548
+ } else {
549
+ for (const pos of positions) {
550
+ if (!validStopPosRegExp.test(pos)) {
551
+ warn(`[mpx-view] linear-gradient 色标位置仅支持百分比 [${color} ${pos}] 已丢弃位置,仅保留颜色`)
552
+ acc.push([color])
553
+ } else {
554
+ acc.push([color, pos])
555
+ }
556
+ }
557
+ }
558
+ return acc
559
+ }, [])
476
560
  .reduce<LinearInfo>((prev, cur, idx, self) => {
477
561
  const { colors, locations } = prev
478
562
  const [color, val] = cur
@@ -502,12 +586,18 @@ function parseLinearGradient (text: string): LinearInfo | undefined {
502
586
  return prev
503
587
  }, { colors: [], locations: [] })
504
588
 
589
+ // 色标全部被丢弃或不足以构成渐变(至少 2 个颜色),降级返回
590
+ if (linearInfo.colors.length < 2) {
591
+ error(`[mpx-view] linear-gradient 至少需要 2 个有效色标,已丢弃,原值: ${text}`)
592
+ return null
593
+ }
594
+
505
595
  return extendObject({}, linearInfo, {
506
596
  direction: direction.trim()
507
597
  })
508
598
  }
509
599
 
510
- function parseBgImage (text: string): {
600
+ function parseBgImage (text?: string): {
511
601
  linearInfo?: LinearInfo
512
602
  direction?: string
513
603
  type?: 'image' | 'linear'
@@ -519,13 +609,21 @@ function parseBgImage (text: string): {
519
609
  if (src) return { src, type: 'image' }
520
610
 
521
611
  const linearInfo = parseLinearGradient(text)
522
- if (!linearInfo) return {}
612
+ if (!linearInfo) {
613
+ // null 表示 parseLinearGradient 内部已报错,外部不要重复;undefined 才属于未识别形态
614
+ if (linearInfo === undefined) {
615
+ error(`[mpx-view] background-image 暂不支持 ${text},已丢弃,仅支持 url(...) / linear-gradient(...)。`)
616
+ }
617
+ return {}
618
+ }
523
619
  return {
524
620
  linearInfo,
525
621
  type: 'linear'
526
622
  }
527
623
  }
528
624
 
625
+ export const __parseBgImageForTest = (text?: string): any => parseBgImage(text)
626
+
529
627
  function normalizeBackgroundSize (
530
628
  backgroundSize: NonNullable<ExtendedViewStyle['backgroundSize']>,
531
629
  type: 'image' | 'linear' | undefined
@@ -546,19 +644,6 @@ function normalizeBackgroundSize (
546
644
  })
547
645
  }
548
646
 
549
- function preParseImage (imageStyle?: ExtendedViewStyle) {
550
- const { backgroundImage = '', backgroundSize = ['auto'], backgroundPosition = [0, 0] } = normalizeStyle(imageStyle) || {}
551
- const { type, src, linearInfo } = parseBgImage(backgroundImage)
552
-
553
- return {
554
- src,
555
- linearInfo,
556
- type,
557
- sizeList: normalizeBackgroundSize(backgroundSize, type),
558
- backgroundPosition: normalizeBackgroundPosition(backgroundPosition)
559
- }
560
- }
561
-
562
647
  function isDiagonalAngle (linearInfo?: LinearInfo): boolean {
563
648
  return !!(linearInfo?.direction && diagonalAngleMap[linearInfo.direction])
564
649
  }
@@ -583,21 +668,48 @@ function inheritStyle (innerStyle: ExtendedViewStyle = {}) {
583
668
  }
584
669
 
585
670
  function useWrapImage (imageStyle?: ExtendedViewStyle, innerStyle?: Record<string, any>, enableFastImage?: boolean) {
586
- // 预处理数据
587
- const preImageInfo: PreImageInfo = preParseImage(imageStyle)
588
- // 预解析
589
- const { src, sizeList, type } = preImageInfo
671
+ const backgroundImage = imageStyle?.backgroundImage
672
+ const backgroundSize = imageStyle?.backgroundSize
673
+ const backgroundPosition = imageStyle?.backgroundPosition
674
+ const parsedImageInfo = useMemo(() => parseBgImage(backgroundImage), [backgroundImage])
675
+ const { src, type } = parsedImageInfo
676
+ const backgroundSizeKey = Array.isArray(backgroundSize) ? backgroundSize.join('|') : backgroundSize
677
+ const sizeList = useMemo(
678
+ () => normalizeBackgroundSize(backgroundSize ?? ['auto'], type),
679
+ [backgroundSizeKey, type]
680
+ )
681
+ const backgroundPositionKey = Array.isArray(backgroundPosition) ? backgroundPosition.join('|') : backgroundPosition
682
+ const bgPosition = useMemo(
683
+ () => normalizeBackgroundPosition(backgroundPosition ?? [0, 0]),
684
+ [backgroundPositionKey]
685
+ )
686
+ const preImageInfo: PreImageInfo = useMemo(
687
+ () => extendObject({}, parsedImageInfo, { sizeList, backgroundPosition: bgPosition }),
688
+ [parsedImageInfo, sizeList, bgPosition]
689
+ )
590
690
 
591
691
  // 判断是否可挂载onLayout
592
692
  const { needLayout, needImageSize } = checkNeedLayout(preImageInfo)
593
693
 
594
694
  const [show, setShow] = useState<boolean>(((type === 'image' && !!src) || type === 'linear') && !needLayout && !needImageSize)
595
- const [, setImageSizeWidth] = useState<number | null>(null)
596
- const [, setImageSizeHeight] = useState<number | null>(null)
597
- const [, setLayoutInfoWidth] = useState<number | null>(null)
598
- const [, setLayoutInfoHeight] = useState<number | null>(null)
695
+ const [version, setVersion] = useState(0)
599
696
  const sizeInfo = useRef<Size | null>(null)
600
697
  const layoutInfo = useRef<Size | null>(null)
698
+ const sizeCacheRef = useRef<Map<string, Size>>(new Map())
699
+ // sizeInfo / layoutInfo / setVersion 都是稳定引用,闭包整个生命周期只分配一次
700
+ const { bumpVersion, setImageSize, setLayoutInfo } = useMemo(() => ({
701
+ bumpVersion: () => setVersion(version => version + 1),
702
+ setImageSize: (width: number, height: number) => {
703
+ if (sizeInfo.current?.width === width && sizeInfo.current?.height === height) return false
704
+ sizeInfo.current = { width, height }
705
+ return true
706
+ },
707
+ setLayoutInfo: (width: number, height: number) => {
708
+ if (layoutInfo.current?.width === width && layoutInfo.current?.height === height) return false
709
+ layoutInfo.current = { width, height }
710
+ return true
711
+ }
712
+ }), [])
601
713
  useEffect(() => {
602
714
  sizeInfo.current = null
603
715
  if (type === 'linear') {
@@ -615,54 +727,55 @@ function useWrapImage (imageStyle?: ExtendedViewStyle, innerStyle?: Record<strin
615
727
  }
616
728
 
617
729
  if (needImageSize) {
618
- Image.getSize(src, (width, height) => {
619
- sizeInfo.current = {
620
- width,
621
- height
730
+ const cached = sizeCacheRef.current.get(src)
731
+ if (cached) {
732
+ const imageSizeChanged = setImageSize(cached.width, cached.height)
733
+ if (!needLayout || layoutInfo.current) {
734
+ imageSizeChanged && bumpVersion()
735
+ setShow(true)
622
736
  }
737
+ return
738
+ }
739
+ let cancelled = false
740
+ Image.getSize(src, (width, height) => {
741
+ // cache 仍然填上,避免下次同 src 再发一次请求
742
+ sizeCacheRef.current.set(src, { width, height })
743
+ if (cancelled) return
744
+ const imageSizeChanged = setImageSize(width, height)
623
745
  // 1. 当需要绑定onLayout 2. 获取到布局信息
624
746
  if (!needLayout || layoutInfo.current) {
625
- setImageSizeWidth(width)
626
- setImageSizeHeight(height)
627
- if (layoutInfo.current) {
628
- setLayoutInfoWidth(layoutInfo.current.width)
629
- setLayoutInfoHeight(layoutInfo.current.height)
630
- }
747
+ imageSizeChanged && bumpVersion()
631
748
  setShow(true)
632
749
  }
633
750
  })
751
+ return () => { cancelled = true }
634
752
  }
635
753
  // type 添加type 处理无渐变 有渐变的场景
636
- }, [src, type])
754
+ }, [src, type, needLayout, needImageSize])
755
+
756
+ const imageProps = useMemo(
757
+ () => imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size),
758
+ [preImageInfo, version]
759
+ )
637
760
 
638
761
  if (!type) return null
639
762
 
640
763
  const onLayout = (res: LayoutChangeEvent) => {
641
764
  const { width, height } = res?.nativeEvent?.layout || {}
642
- layoutInfo.current = {
643
- width,
644
- height
645
- }
765
+ // layoutInfo 总是更新,Image.getSize 回调要靠 layoutInfo.current 决定是否 setShow
766
+ let changed = setLayoutInfo(width, height)
646
767
  if (!needImageSize) {
647
- setLayoutInfoWidth(width)
648
- setLayoutInfoHeight(height)
649
768
  // 有渐变角度的时候,才触发渲染组件
650
769
  if (type === 'linear') {
651
- sizeInfo.current = {
652
- width: calcPercent(sizeList[0] as NumberVal, width),
653
- height: calcPercent(sizeList[1] as NumberVal, height)
654
- }
655
- setImageSizeWidth(sizeInfo.current.width)
656
- setImageSizeHeight(sizeInfo.current.height)
770
+ changed = setImageSize(calcPercent(sizeList[0] as NumberVal, width), calcPercent(sizeList[1] as NumberVal, height)) || changed
657
771
  }
772
+ changed && bumpVersion()
658
773
  setShow(true)
659
774
  } else if (sizeInfo.current) {
660
- setLayoutInfoWidth(width)
661
- setLayoutInfoHeight(height)
662
- setImageSizeWidth(sizeInfo.current.width)
663
- setImageSizeHeight(sizeInfo.current.height)
775
+ changed && bumpVersion()
664
776
  setShow(true)
665
777
  }
778
+ // needImageSize && !sizeInfo.current:等 Image.getSize 回调里 setShow(true),此处不触发渲染
666
779
  }
667
780
 
668
781
  const backgroundProps: ViewProps = extendObject({ key: 'backgroundImage' }, needLayout ? { onLayout } : {},
@@ -670,33 +783,33 @@ function useWrapImage (imageStyle?: ExtendedViewStyle, innerStyle?: Record<strin
670
783
  )
671
784
 
672
785
  return createElement(View, backgroundProps,
673
- show && type === 'linear' && createElement(LinearGradient, extendObject({ useAngle: true }, imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size))),
674
- show && type === 'image' && renderImage(imageStyleToProps(preImageInfo, sizeInfo.current as Size, layoutInfo.current as Size), enableFastImage)
786
+ show && type === 'linear' && createElement(LinearGradient, extendObject({ useAngle: true }, imageProps as LinearImageProps)),
787
+ show && type === 'image' && renderImage(imageProps, enableFastImage)
675
788
  )
676
789
  }
677
790
 
678
791
  interface WrapChildrenConfig {
679
792
  hasVarDec: boolean
680
793
  enableBackground?: boolean
681
- textStyle?: TextStyle
682
794
  backgroundStyle?: ExtendedViewStyle
683
795
  varContext?: Record<string, any>
684
- textProps?: Record<string, any>
685
- textPassThrough?: ReturnType<typeof useTextPassThroughValue>
796
+ textPassThrough?: TextPassThroughContextValue | null
686
797
  innerStyle?: Record<string, any>
687
798
  enableFastImage?: boolean
688
799
  }
689
800
 
690
801
  function wrapWithChildren (props: _ViewProps, { hasVarDec, enableBackground, backgroundStyle, varContext, textPassThrough, innerStyle, enableFastImage }: WrapChildrenConfig) {
691
- const children = wrapChildren(props, {
802
+ const children = wrapChildren(props.children, {
692
803
  hasVarDec,
693
804
  varContext,
694
805
  textPassThrough
695
806
  })
696
807
 
808
+ if (!enableBackground) return children
809
+
697
810
  return [
698
811
  // eslint-disable-next-line react-hooks/rules-of-hooks
699
- enableBackground ? useWrapImage(backgroundStyle, innerStyle, enableFastImage) : null,
812
+ useWrapImage(backgroundStyle, innerStyle, enableFastImage),
700
813
  children
701
814
  ]
702
815
  }
@@ -716,11 +829,10 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
716
829
  'hover-start-time': hoverStartTime = 50,
717
830
  'hover-stay-time': hoverStayTime = 400,
718
831
  'enable-var': enableVar,
719
- 'external-var-context': externalVarContext,
720
832
  'enable-background': enableBackground,
833
+ 'enable-text-pass-through': enableTextPassThrough,
721
834
  'enable-fast-image': enableFastImage,
722
835
  'enable-animation': enableAnimation,
723
- 'parent-font-size': parentFontSize,
724
836
  'parent-width': parentWidth,
725
837
  'parent-height': parentHeight,
726
838
  animation,
@@ -728,20 +840,12 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
728
840
  bindtransitionend
729
841
  } = props
730
842
 
731
- // 默认样式
732
- const defaultStyle: ExtendedViewStyle = style.display === 'flex'
733
- ? {
734
- flexDirection: 'row',
735
- flexBasis: 'auto',
736
- flexShrink: 1,
737
- flexWrap: 'nowrap'
738
- }
739
- : {}
740
-
741
843
  const enableHover = !!hoverStyle
742
844
  const { isHover, gesture } = useHover({ enableHover, hoverStartTime, hoverStayTime })
743
845
 
744
- const styleObj: ExtendedViewStyle = extendObject({}, defaultStyle, style, isHover ? hoverStyle as ExtendedViewStyle : {})
846
+ const styleObj: ExtendedViewStyle = isHover
847
+ ? extendObject({}, style, hoverStyle as ExtendedViewStyle)
848
+ : style
745
849
  if (__mpx_perf_framework__) perf.scopeEnd(idProps)
746
850
 
747
851
  // ───── style 阶段 ─────
@@ -757,14 +861,17 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
757
861
  setHeight
758
862
  } = useTransformStyle(styleObj, {
759
863
  enableVar,
760
- externalVarContext,
761
- parentFontSize,
762
864
  parentWidth,
763
- parentHeight
865
+ parentHeight,
866
+ // 基于合并后的 styleObj 判断(hover 状态切换 display 也能触发)
867
+ // 用户传 flex shorthand 时使用精简 default,避免 flexBasis/flexShrink 反向覆盖
868
+ defaultStyle: styleObj.display === 'flex'
869
+ ? (hasOwn(styleObj, 'flex') ? FLEX_DEFAULT_STYLE_TRIMMED : FLEX_DEFAULT_STYLE)
870
+ : undefined
764
871
  })
765
872
 
766
873
  const { textStyle, backgroundStyle, innerStyle = {} } = splitStyle(normalStyle)
767
- const textPassThrough = useTextPassThroughValue(textStyle, textProps)
874
+ const textPassThrough = useTextPassThrough(textStyle, textProps, { enableTextPassThrough })
768
875
 
769
876
  enableBackground = enableBackground || !!backgroundStyle
770
877
  const enableBackgroundRef = useRef(enableBackground)
@@ -783,18 +890,16 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
783
890
  layoutProps
784
891
  } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef })
785
892
 
786
- const viewStyle = extendObject({}, innerStyle, layoutStyle)
787
- const transitionend = isFunction(catchtransitionend)
788
- ? catchtransitionend
789
- : isFunction(bindtransitionend)
790
- ? bindtransitionend
791
- : undefined
893
+ const viewStyle = layoutStyle
894
+ ? extendObject({}, innerStyle, layoutStyle)
895
+ : innerStyle
792
896
  const { enableStyleAnimation, animationStyle } = useAnimationHooks({
793
897
  layoutRef,
794
898
  animation,
795
899
  enableAnimation,
796
900
  style: viewStyle,
797
- transitionend
901
+ catchtransitionend,
902
+ bindtransitionend
798
903
  })
799
904
  if (__mpx_perf_framework__) perf.scopeEnd(idStyle)
800
905
 
@@ -812,15 +917,16 @@ const _View = forwardRef<HandlerRef<View, _ViewProps>, _ViewProps>((viewProps, r
812
917
  }
813
918
  ),
814
919
  [
815
- 'animation',
816
920
  'hover-start-time',
817
921
  'hover-stay-time',
818
922
  'hover-style',
819
923
  'hover-class',
820
- 'enable-fast-image',
924
+ 'enable-background',
821
925
  'enable-animation',
822
- 'bindtransitionend',
823
- 'catchtransitionend'
926
+ 'enable-fast-image',
927
+ 'animation',
928
+ 'catchtransitionend',
929
+ 'bindtransitionend'
824
930
  ],
825
931
  {
826
932
  layoutRef