@mpxjs/webpack-plugin 2.10.24-beta.1 → 2.10.24-test.1

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