@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
@@ -80,18 +80,18 @@ const styles = StyleSheet.create({
80
80
  });
81
81
  const MpxVideo = forwardRef((videoProps, ref) => {
82
82
  const { innerProps: props = {} } = splitProps(videoProps);
83
- const { src, autoplay = false, loop = false, muted = false, controls = true, poster = '', bindplay, bindpause, bindended, bindtimeupdate, bindfullscreenchange, bindwaiting, binderror, bindloadedmetadata, bindcontrolstoggle, bindseekcomplete, style, 'initial-time': initialTime = 0, 'object-fit': objectFit = 'contain', 'is-drm': isDrm = false, 'provision-url': provisionUrl, 'certificate-url': certificateUrl, 'license-url': licenseUrl, 'preferred-peak-bit-rate': preferredPeakBitRate = 0, 'enable-auto-rotation': enableAutoRotation = false, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
83
+ const { src, autoplay = false, loop = false, muted = false, controls = true, poster = '', bindplay, bindpause, bindended, bindtimeupdate, bindfullscreenchange, bindwaiting, binderror, bindloadedmetadata, bindcontrolstoggle, bindseekcomplete, style, 'initial-time': initialTime = 0, 'object-fit': objectFit = 'contain', 'is-drm': isDrm = false, 'provision-url': provisionUrl, 'certificate-url': certificateUrl, 'license-url': licenseUrl, 'preferred-peak-bit-rate': preferredPeakBitRate = 0, 'enable-auto-rotation': enableAutoRotation = false, 'enable-var': enableVar, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight } = props;
84
84
  const videoRef = useRef(null);
85
85
  const viewRef = useRef(null);
86
86
  const videoInfoRef = useRef({});
87
87
  const propsRef = useRef({});
88
88
  propsRef.current = props;
89
- const { normalStyle, hasSelfPercent, setWidth, setHeight, hasPositionFixed } = useTransformStyle(extendObject({}, styles.container, style), {
89
+ const { normalStyle, hasSelfPercent, setWidth, setHeight, hasPositionFixed } = useTransformStyle(style, {
90
90
  enableVar,
91
- externalVarContext,
92
91
  parentFontSize,
93
92
  parentWidth,
94
- parentHeight
93
+ parentHeight,
94
+ defaultStyle: styles.container
95
95
  });
96
96
  const { layoutRef, layoutStyle, layoutProps } = useLayout({
97
97
  props,
@@ -18,9 +18,9 @@ export interface _ViewProps extends ViewProps {
18
18
  'hover-start-time'?: number;
19
19
  'hover-stay-time'?: number;
20
20
  'enable-background'?: boolean;
21
+ 'enable-text-pass-through'?: boolean;
21
22
  'enable-var'?: boolean;
22
23
  'enable-fast-image'?: boolean;
23
- 'external-var-context'?: Record<string, any>;
24
24
  'parent-font-size'?: number;
25
25
  'parent-width'?: number;
26
26
  'parent-height'?: number;
@@ -31,5 +31,6 @@ export interface _ViewProps extends ViewProps {
31
31
  bindtransitionend?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void;
32
32
  catchtransitionend?: (event: NativeSyntheticEvent<TouchEvent> | unknown) => void;
33
33
  }
34
+ export declare const __parseBgImageForTest: (text?: string) => any;
34
35
  declare const _View: import("react").ForwardRefExoticComponent<_ViewProps & import("react").RefAttributes<HandlerRef<View, _ViewProps>>>;
35
36
  export default _View;
@@ -5,13 +5,13 @@
5
5
  * ✔ hover-stay-time
6
6
  */
7
7
  import { View, StyleSheet, Image } from 'react-native';
8
- import { useRef, useState, useEffect, forwardRef, createElement } from 'react';
8
+ import { useRef, useState, useEffect, useMemo, forwardRef, createElement } from 'react';
9
9
  import useInnerProps from './getInnerListeners';
10
10
  import Animated from 'react-native-reanimated';
11
11
  import useAnimationHooks from './animationHooks/index';
12
12
  import useNodesRef from './useNodesRef';
13
- import { parseUrl, PERCENT_REGEX, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout, renderImage, pickStyle, extendObject, useHover, useTextPassThroughValue } from './utils';
14
- import { error, isFunction } from '@mpxjs/utils';
13
+ import { parseUrl, percentRegExp, splitStyle, splitProps, useTransformStyle, wrapChildren, useLayout, renderImage, pickStyle, extendObject, useHover, useTextPassThrough } from './utils';
14
+ import { error, warn, hasOwn } from '@mpxjs/utils';
15
15
  import * as perf from '@mpxjs/perf';
16
16
  import LinearGradient from 'react-native-linear-gradient';
17
17
  import { GestureDetector } from 'react-native-gesture-handler';
@@ -22,6 +22,18 @@ const linearMap = new Map([
22
22
  ['left', 270],
23
23
  ['right', 90]
24
24
  ]);
25
+ const FLEX_DEFAULT_STYLE = {
26
+ flexDirection: 'row',
27
+ flexBasis: 'auto',
28
+ flexShrink: 1,
29
+ flexWrap: 'nowrap'
30
+ };
31
+ // 用户传入 flex shorthand 时使用的精简版(裁掉 flexBasis/flexShrink)
32
+ // 避免 number 形式 flex:1 被 default flexBasis:'auto' 反向覆盖
33
+ const FLEX_DEFAULT_STYLE_TRIMMED = {
34
+ flexDirection: 'row',
35
+ flexWrap: 'nowrap'
36
+ };
25
37
  // 对角线角度
26
38
  const diagonalAngleMap = {
27
39
  'top right': (width, height) => {
@@ -40,22 +52,35 @@ const diagonalAngleMap = {
40
52
  function radToAngle(r) {
41
53
  return r * 180 / Math.PI;
42
54
  }
43
- const applyHandlers = (handlers, args) => {
44
- for (const handler of handlers) {
45
- handler(...args);
55
+ // === linear-gradient 解析相关正则(模块级,一次创建) ===
56
+ // CSS <angle> 支持 deg/turn/rad/grad 四种单位,统一归一为 deg
57
+ // https://www.w3.org/TR/css-values-3/#angles
58
+ const angleRegExp = /^\s*(-?\d+(?:\.\d+)?)(deg|turn|rad|grad)\b/;
59
+ const gradientToRegExp = /^to\b\s*/;
60
+ // 入口形态:仅在含 linear-gradient 时走解析;提取括号内内容
61
+ const linearGradientRegExp = /linear-gradient\((.*)\)/;
62
+ // 顶层逗号切分(忽略 () / # 内部的逗号)
63
+ const topLevelCommaRegExp = /,(?![^(#]*\))/;
64
+ // 色标内空白切分(忽略色函数里的逗号边界)
65
+ const stopWhitespaceRegExp = /(?<!,)\s+/;
66
+ // 色标位置仅支持 `<n>%` 或裸 `0`(CSS 允许 0 作为唯一无单位 length)
67
+ // 其它单位(px/rpx/em/vw/...)当前无法在不依赖 layout 的情况下归一为百分比,统一拒绝
68
+ const validStopPosRegExp = /^-?\d+(?:\.\d+)?%$|^0$/;
69
+ // color hint 检测:色标 token 仅含一个分量且以数字/正负号/小数点开头
70
+ const colorHintLeadingRegExp = /^[-+.\d]/;
71
+ function normalizeAngle(raw) {
72
+ const m = raw.match(angleRegExp);
73
+ if (!m)
74
+ return undefined;
75
+ const n = +m[1];
76
+ switch (m[2]) {
77
+ case 'turn': return n * 360;
78
+ case 'rad': return radToAngle(n);
79
+ case 'grad': return n * 0.9;
80
+ default: return n; // deg
46
81
  }
47
- };
48
- const normalizeStyle = (style = {}) => {
49
- ['backgroundSize', 'backgroundPosition'].forEach(name => {
50
- if (style[name] && typeof style[name] === 'string') {
51
- if (style[name].trim()) {
52
- style[name] = style[name].split(' ');
53
- }
54
- }
55
- });
56
- return style;
57
- };
58
- const isPercent = (val) => typeof val === 'string' && PERCENT_REGEX.test(val);
82
+ }
83
+ const isPercent = (val) => typeof val === 'string' && percentRegExp.test(val);
59
84
  const isBackgroundSizeKeyword = (val) => typeof val === 'string' && /^cover|contain$/.test(val);
60
85
  const isNeedLayout = (preImageInfo) => {
61
86
  const { sizeList, backgroundPosition, linearInfo, type } = preImageInfo;
@@ -167,13 +192,13 @@ function backgroundSize(imageProps, preImageInfo, imageSize, layoutInfo) {
167
192
  // 枚举值
168
193
  if (typeof width === 'string' && ['cover', 'contain'].includes(width)) {
169
194
  if (layoutInfo && imageSize) {
170
- const layoutRatio = layoutWidth / imageSizeWidth;
171
- const eleRatio = imageSizeWidth / imageSizeHeight;
172
- // 容器宽高比 大于 图片的宽高比,依据宽度作为基准,否则以高度为基准
173
- if ((layoutRatio <= eleRatio && width === 'contain') || (layoutRatio >= eleRatio && width === 'cover')) {
195
+ const containerRatio = layoutWidth / layoutHeight;
196
+ const imageRatio = imageSizeWidth / imageSizeHeight;
197
+ // 容器宽高比 小于等于 图片宽高比:contain 按宽缩放,cover 按高缩放
198
+ if ((containerRatio <= imageRatio && width === 'contain') || (containerRatio >= imageRatio && width === 'cover')) {
174
199
  dimensions = calculateSize(layoutWidth, imageSizeHeight / imageSizeWidth, true);
175
200
  }
176
- else if ((layoutRatio > eleRatio && width === 'contain') || (layoutRatio < eleRatio && width === 'cover')) {
201
+ else if ((containerRatio > imageRatio && width === 'contain') || (containerRatio < imageRatio && width === 'cover')) {
177
202
  dimensions = calculateSize(layoutHeight, imageSizeWidth / imageSizeHeight);
178
203
  }
179
204
  }
@@ -238,7 +263,17 @@ function linearGradient(imageProps, preImageInfo, imageSize, layoutInfo) {
238
263
  if (type !== 'linear')
239
264
  return;
240
265
  // 角度计算
241
- let angle = +(linearMap.get(direction) || direction.match(/(-?\d+(\.\d+)?)deg/)?.[1] || 180) % 360;
266
+ // 注意:不要用 `linearMap.get(direction) || ...`,因为 'top' 映射为 0 是 falsy 值,
267
+ // 会被误判为未命中并落到默认 180deg
268
+ let angle;
269
+ if (linearMap.has(direction)) {
270
+ angle = linearMap.get(direction);
271
+ }
272
+ else {
273
+ const normalized = normalizeAngle(direction);
274
+ angle = normalized !== undefined ? normalized : 180;
275
+ }
276
+ angle = angle % 360;
242
277
  // 对角线角度计算
243
278
  if (layoutInfo && diagonalAngleMap[direction] && imageSize && linearInfo) {
244
279
  angle = radToAngle(diagonalAngleMap[direction](width, height)) || 180;
@@ -249,16 +284,29 @@ function linearGradient(imageProps, preImageInfo, imageSize, layoutInfo) {
249
284
  imageProps.angle = angle;
250
285
  }
251
286
  const imageStyleToProps = (preImageInfo, imageSize, layoutInfo) => {
252
- // 初始化
287
+ const { type } = preImageInfo;
253
288
  const imageProps = {
254
- resizeMode: 'cover',
255
289
  style: {
256
290
  position: 'absolute'
257
291
  // ...StyleSheet.absoluteFillObject
258
- },
259
- colors: []
292
+ }
260
293
  };
261
- applyHandlers([backgroundSize, backgroundImage, backgroundPosition, linearGradient], [imageProps, preImageInfo, imageSize, layoutInfo]);
294
+ if (type === 'image') {
295
+ imageProps.resizeMode = 'cover';
296
+ }
297
+ else {
298
+ imageProps.colors = [];
299
+ }
300
+ backgroundSize(imageProps, preImageInfo, imageSize, layoutInfo);
301
+ if (preImageInfo.backgroundPosition.length) {
302
+ backgroundPosition(imageProps, preImageInfo, imageSize, layoutInfo);
303
+ }
304
+ if (type === 'image') {
305
+ backgroundImage(imageProps, preImageInfo);
306
+ }
307
+ else {
308
+ linearGradient(imageProps, preImageInfo, imageSize, layoutInfo);
309
+ }
262
310
  return imageProps;
263
311
  };
264
312
  function isHorizontal(val) {
@@ -364,24 +412,61 @@ function calcSteps(startVal, endVal, len) {
364
412
  }
365
413
  return newArr;
366
414
  }
415
+ // 返回值约定:
416
+ // - LinearInfo 解析成功
417
+ // - null 已识别为 linear-gradient 但内部已通过 error/warn 报错,外部不要再报
418
+ // - undefined 不是 linear-gradient 形态,外部按"不支持的 background-image"处理
367
419
  function parseLinearGradient(text) {
368
- let linearText = text.trim().match(/linear-gradient\((.*)\)/)?.[1];
420
+ let linearText = text.trim().match(linearGradientRegExp)?.[1];
369
421
  if (!linearText)
370
422
  return;
371
- // 添加默认的角度
372
- if (!/^to|^-?\d+deg/.test(linearText)) {
373
- linearText = '180deg ,' + linearText;
374
- }
375
- else {
376
- linearText = linearText.replace('to', '');
423
+ // 多重渐变 / 多重背景:linearGradientRegExp 用了贪婪 `.*`,多渐变时括号内
424
+ // 会贪到下一段 `linear-gradient(`,单段则一定不含;用 includes 一次判断即可
425
+ if (linearText.includes('linear-gradient(')) {
426
+ error(`[mpx-view] background-image 暂不支持多重渐变 (multi gradients),已丢弃,原值: ${text}`);
427
+ return null;
377
428
  }
378
429
  // 把 0deg, red 10%, blue 20% 解析为 ['0deg', 'red, 10%', 'blue, 20%']
379
- const [direction, ...colorList] = linearText.split(/,(?![^(#]*\))/);
430
+ if (gradientToRegExp.test(linearText)) {
431
+ linearText = linearText.replace(gradientToRegExp, '');
432
+ }
433
+ else if (!angleRegExp.test(linearText)) {
434
+ linearText = '180deg ,' + linearText;
435
+ }
436
+ const [direction, ...colorList] = linearText.split(topLevelCommaRegExp);
380
437
  // 记录需要填充起点的起始位置
381
438
  let startIdx = 0;
382
439
  let startVal = 0;
383
440
  // 把 ['red, 10%', 'blue, 20%']解析为 [[red, 10%], [blue, 20%]]
384
- const linearInfo = colorList.map(item => item.trim().split(/(?<!,)\s+/))
441
+ // 双位置语法展开:['red', '0%', '50%'] [['red', '0%'], ['red', '50%']]
442
+ // 等价于 CSS Images Level 4 中 `red 0% 50%` ≡ `red 0%, red 50%`
443
+ // 同步对不支持的写法做拦截:color hint(仅一个位置 token)、非百分比位置(px/rpx 等)
444
+ const linearInfo = colorList
445
+ .map(item => item.trim().split(stopWhitespaceRegExp))
446
+ .reduce((acc, parts) => {
447
+ // color hint: 例如 `linear-gradient(red, 30%, blue)` 中的 `30%`
448
+ // 该 token 仅含一个位置值、无颜色;当前实现无法做颜色采样,直接丢弃
449
+ if (parts.length === 1 && colorHintLeadingRegExp.test(parts[0])) {
450
+ warn(`[mpx-view] linear-gradient 暂不支持 color hint 写法 [${parts[0]}],已丢弃该色标`);
451
+ return acc;
452
+ }
453
+ const [color, ...positions] = parts;
454
+ if (positions.length === 0) {
455
+ acc.push([color]);
456
+ }
457
+ else {
458
+ for (const pos of positions) {
459
+ if (!validStopPosRegExp.test(pos)) {
460
+ warn(`[mpx-view] linear-gradient 色标位置仅支持百分比 [${color} ${pos}] 已丢弃位置,仅保留颜色`);
461
+ acc.push([color]);
462
+ }
463
+ else {
464
+ acc.push([color, pos]);
465
+ }
466
+ }
467
+ }
468
+ return acc;
469
+ }, [])
385
470
  .reduce((prev, cur, idx, self) => {
386
471
  const { colors, locations } = prev;
387
472
  const [color, val] = cur;
@@ -406,6 +491,11 @@ function parseLinearGradient(text) {
406
491
  !isNaN(numberVal) && locations.push(numberVal);
407
492
  return prev;
408
493
  }, { colors: [], locations: [] });
494
+ // 色标全部被丢弃或不足以构成渐变(至少 2 个颜色),降级返回
495
+ if (linearInfo.colors.length < 2) {
496
+ error(`[mpx-view] linear-gradient 至少需要 2 个有效色标,已丢弃,原值: ${text}`);
497
+ return null;
498
+ }
409
499
  return extendObject({}, linearInfo, {
410
500
  direction: direction.trim()
411
501
  });
@@ -417,13 +507,19 @@ function parseBgImage(text) {
417
507
  if (src)
418
508
  return { src, type: 'image' };
419
509
  const linearInfo = parseLinearGradient(text);
420
- if (!linearInfo)
510
+ if (!linearInfo) {
511
+ // null 表示 parseLinearGradient 内部已报错,外部不要重复;undefined 才属于未识别形态
512
+ if (linearInfo === undefined) {
513
+ error(`[mpx-view] background-image 暂不支持 ${text},已丢弃,仅支持 url(...) / linear-gradient(...)。`);
514
+ }
421
515
  return {};
516
+ }
422
517
  return {
423
518
  linearInfo,
424
519
  type: 'linear'
425
520
  };
426
521
  }
522
+ export const __parseBgImageForTest = (text) => parseBgImage(text);
427
523
  function normalizeBackgroundSize(backgroundSize, type) {
428
524
  const sizeList = backgroundSize.slice();
429
525
  if (sizeList.length === 1)
@@ -439,17 +535,6 @@ function normalizeBackgroundSize(backgroundSize, type) {
439
535
  return global.__formatValue(val);
440
536
  });
441
537
  }
442
- function preParseImage(imageStyle) {
443
- const { backgroundImage = '', backgroundSize = ['auto'], backgroundPosition = [0, 0] } = normalizeStyle(imageStyle) || {};
444
- const { type, src, linearInfo } = parseBgImage(backgroundImage);
445
- return {
446
- src,
447
- linearInfo,
448
- type,
449
- sizeList: normalizeBackgroundSize(backgroundSize, type),
450
- backgroundPosition: normalizeBackgroundPosition(backgroundPosition)
451
- };
452
- }
453
538
  function isDiagonalAngle(linearInfo) {
454
539
  return !!(linearInfo?.direction && diagonalAngleMap[linearInfo.direction]);
455
540
  }
@@ -470,19 +555,39 @@ function inheritStyle(innerStyle = {}) {
470
555
  : undefined);
471
556
  }
472
557
  function useWrapImage(imageStyle, innerStyle, enableFastImage) {
473
- // 预处理数据
474
- const preImageInfo = preParseImage(imageStyle);
475
- // 预解析
476
- const { src, sizeList, type } = preImageInfo;
558
+ const backgroundImage = imageStyle?.backgroundImage;
559
+ const backgroundSize = imageStyle?.backgroundSize;
560
+ const backgroundPosition = imageStyle?.backgroundPosition;
561
+ const parsedImageInfo = useMemo(() => parseBgImage(backgroundImage), [backgroundImage]);
562
+ const { src, type } = parsedImageInfo;
563
+ const backgroundSizeKey = Array.isArray(backgroundSize) ? backgroundSize.join('|') : backgroundSize;
564
+ const sizeList = useMemo(() => normalizeBackgroundSize(backgroundSize ?? ['auto'], type), [backgroundSizeKey, type]);
565
+ const backgroundPositionKey = Array.isArray(backgroundPosition) ? backgroundPosition.join('|') : backgroundPosition;
566
+ const bgPosition = useMemo(() => normalizeBackgroundPosition(backgroundPosition ?? [0, 0]), [backgroundPositionKey]);
567
+ const preImageInfo = useMemo(() => extendObject({}, parsedImageInfo, { sizeList, backgroundPosition: bgPosition }), [parsedImageInfo, sizeList, bgPosition]);
477
568
  // 判断是否可挂载onLayout
478
569
  const { needLayout, needImageSize } = checkNeedLayout(preImageInfo);
479
570
  const [show, setShow] = useState(((type === 'image' && !!src) || type === 'linear') && !needLayout && !needImageSize);
480
- const [, setImageSizeWidth] = useState(null);
481
- const [, setImageSizeHeight] = useState(null);
482
- const [, setLayoutInfoWidth] = useState(null);
483
- const [, setLayoutInfoHeight] = useState(null);
571
+ const [version, setVersion] = useState(0);
484
572
  const sizeInfo = useRef(null);
485
573
  const layoutInfo = useRef(null);
574
+ const sizeCacheRef = useRef(new Map());
575
+ // sizeInfo / layoutInfo / setVersion 都是稳定引用,闭包整个生命周期只分配一次
576
+ const { bumpVersion, setImageSize, setLayoutInfo } = useMemo(() => ({
577
+ bumpVersion: () => setVersion(version => version + 1),
578
+ setImageSize: (width, height) => {
579
+ if (sizeInfo.current?.width === width && sizeInfo.current?.height === height)
580
+ return false;
581
+ sizeInfo.current = { width, height };
582
+ return true;
583
+ },
584
+ setLayoutInfo: (width, height) => {
585
+ if (layoutInfo.current?.width === width && layoutInfo.current?.height === height)
586
+ return false;
587
+ layoutInfo.current = { width, height };
588
+ return true;
589
+ }
590
+ }), []);
486
591
  useEffect(() => {
487
592
  sizeInfo.current = null;
488
593
  if (type === 'linear') {
@@ -500,67 +605,67 @@ function useWrapImage(imageStyle, innerStyle, enableFastImage) {
500
605
  return;
501
606
  }
502
607
  if (needImageSize) {
608
+ const cached = sizeCacheRef.current.get(src);
609
+ if (cached) {
610
+ const imageSizeChanged = setImageSize(cached.width, cached.height);
611
+ if (!needLayout || layoutInfo.current) {
612
+ imageSizeChanged && bumpVersion();
613
+ setShow(true);
614
+ }
615
+ return;
616
+ }
617
+ let cancelled = false;
503
618
  Image.getSize(src, (width, height) => {
504
- sizeInfo.current = {
505
- width,
506
- height
507
- };
619
+ // cache 仍然填上,避免下次同 src 再发一次请求
620
+ sizeCacheRef.current.set(src, { width, height });
621
+ if (cancelled)
622
+ return;
623
+ const imageSizeChanged = setImageSize(width, height);
508
624
  // 1. 当需要绑定onLayout 2. 获取到布局信息
509
625
  if (!needLayout || layoutInfo.current) {
510
- setImageSizeWidth(width);
511
- setImageSizeHeight(height);
512
- if (layoutInfo.current) {
513
- setLayoutInfoWidth(layoutInfo.current.width);
514
- setLayoutInfoHeight(layoutInfo.current.height);
515
- }
626
+ imageSizeChanged && bumpVersion();
516
627
  setShow(true);
517
628
  }
518
629
  });
630
+ return () => { cancelled = true; };
519
631
  }
520
632
  // type 添加type 处理无渐变 有渐变的场景
521
- }, [src, type]);
633
+ }, [src, type, needLayout, needImageSize]);
634
+ const imageProps = useMemo(() => imageStyleToProps(preImageInfo, sizeInfo.current, layoutInfo.current), [preImageInfo, version]);
522
635
  if (!type)
523
636
  return null;
524
637
  const onLayout = (res) => {
525
638
  const { width, height } = res?.nativeEvent?.layout || {};
526
- layoutInfo.current = {
527
- width,
528
- height
529
- };
639
+ // layoutInfo 总是更新,Image.getSize 回调要靠 layoutInfo.current 决定是否 setShow
640
+ let changed = setLayoutInfo(width, height);
530
641
  if (!needImageSize) {
531
- setLayoutInfoWidth(width);
532
- setLayoutInfoHeight(height);
533
642
  // 有渐变角度的时候,才触发渲染组件
534
643
  if (type === 'linear') {
535
- sizeInfo.current = {
536
- width: calcPercent(sizeList[0], width),
537
- height: calcPercent(sizeList[1], height)
538
- };
539
- setImageSizeWidth(sizeInfo.current.width);
540
- setImageSizeHeight(sizeInfo.current.height);
644
+ changed = setImageSize(calcPercent(sizeList[0], width), calcPercent(sizeList[1], height)) || changed;
541
645
  }
646
+ changed && bumpVersion();
542
647
  setShow(true);
543
648
  }
544
649
  else if (sizeInfo.current) {
545
- setLayoutInfoWidth(width);
546
- setLayoutInfoHeight(height);
547
- setImageSizeWidth(sizeInfo.current.width);
548
- setImageSizeHeight(sizeInfo.current.height);
650
+ changed && bumpVersion();
549
651
  setShow(true);
550
652
  }
653
+ // needImageSize && !sizeInfo.current:等 Image.getSize 回调里 setShow(true),此处不触发渲染
551
654
  };
552
655
  const backgroundProps = extendObject({ key: 'backgroundImage' }, needLayout ? { onLayout } : {}, { style: extendObject({}, inheritStyle(innerStyle), StyleSheet.absoluteFillObject, { overflow: 'hidden' }) });
553
- return createElement(View, backgroundProps, show && type === 'linear' && createElement(LinearGradient, extendObject({ useAngle: true }, imageStyleToProps(preImageInfo, sizeInfo.current, layoutInfo.current))), show && type === 'image' && renderImage(imageStyleToProps(preImageInfo, sizeInfo.current, layoutInfo.current), enableFastImage));
656
+ return createElement(View, backgroundProps, show && type === 'linear' && createElement(LinearGradient, extendObject({ useAngle: true }, imageProps)), show && type === 'image' && renderImage(imageProps, enableFastImage));
554
657
  }
555
658
  function wrapWithChildren(props, { hasVarDec, enableBackground, backgroundStyle, varContext, textPassThrough, innerStyle, enableFastImage }) {
556
- const children = wrapChildren(props, {
659
+ const children = wrapChildren(props.children, {
557
660
  hasVarDec,
558
661
  varContext,
559
662
  textPassThrough
560
663
  });
664
+ if (!enableBackground)
665
+ return children;
561
666
  return [
562
667
  // eslint-disable-next-line react-hooks/rules-of-hooks
563
- enableBackground ? useWrapImage(backgroundStyle, innerStyle, enableFastImage) : null,
668
+ useWrapImage(backgroundStyle, innerStyle, enableFastImage),
564
669
  children
565
670
  ];
566
671
  }
@@ -574,19 +679,12 @@ const _View = forwardRef((viewProps, ref) => {
574
679
  if (__mpx_perf_framework__)
575
680
  idProps = perf.scopeStart('view:render:props');
576
681
  const { textProps, innerProps: props = {} } = splitProps(viewProps);
577
- let { style = {}, 'hover-style': hoverStyle, 'hover-start-time': hoverStartTime = 50, 'hover-stay-time': hoverStayTime = 400, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'enable-background': enableBackground, 'enable-fast-image': enableFastImage, 'enable-animation': enableAnimation, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, animation, catchtransitionend, bindtransitionend } = props;
578
- // 默认样式
579
- const defaultStyle = style.display === 'flex'
580
- ? {
581
- flexDirection: 'row',
582
- flexBasis: 'auto',
583
- flexShrink: 1,
584
- flexWrap: 'nowrap'
585
- }
586
- : {};
682
+ let { style = {}, 'hover-style': hoverStyle, 'hover-start-time': hoverStartTime = 50, 'hover-stay-time': hoverStayTime = 400, 'enable-var': enableVar, 'enable-background': enableBackground, 'enable-text-pass-through': enableTextPassThrough, 'enable-fast-image': enableFastImage, 'enable-animation': enableAnimation, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, animation, catchtransitionend, bindtransitionend } = props;
587
683
  const enableHover = !!hoverStyle;
588
684
  const { isHover, gesture } = useHover({ enableHover, hoverStartTime, hoverStayTime });
589
- const styleObj = extendObject({}, defaultStyle, style, isHover ? hoverStyle : {});
685
+ const styleObj = isHover
686
+ ? extendObject({}, style, hoverStyle)
687
+ : style;
590
688
  if (__mpx_perf_framework__)
591
689
  perf.scopeEnd(idProps);
592
690
  // ───── style 阶段 ─────
@@ -595,13 +693,17 @@ const _View = forwardRef((viewProps, ref) => {
595
693
  idStyle = perf.scopeStart('view:render:style');
596
694
  const { normalStyle, hasSelfPercent, hasPositionFixed, hasVarDec, varContextRef, setWidth, setHeight } = useTransformStyle(styleObj, {
597
695
  enableVar,
598
- externalVarContext,
599
696
  parentFontSize,
600
697
  parentWidth,
601
- parentHeight
698
+ parentHeight,
699
+ // 基于合并后的 styleObj 判断(hover 状态切换 display 也能触发)
700
+ // 用户传 flex shorthand 时使用精简 default,避免 flexBasis/flexShrink 反向覆盖
701
+ defaultStyle: styleObj.display === 'flex'
702
+ ? (hasOwn(styleObj, 'flex') ? FLEX_DEFAULT_STYLE_TRIMMED : FLEX_DEFAULT_STYLE)
703
+ : undefined
602
704
  });
603
705
  const { textStyle, backgroundStyle, innerStyle = {} } = splitStyle(normalStyle);
604
- const textPassThrough = useTextPassThroughValue(textStyle, textProps);
706
+ const textPassThrough = useTextPassThrough(textStyle, textProps, { enableTextPassThrough });
605
707
  enableBackground = enableBackground || !!backgroundStyle;
606
708
  const enableBackgroundRef = useRef(enableBackground);
607
709
  if (enableBackgroundRef.current !== enableBackground) {
@@ -612,18 +714,16 @@ const _View = forwardRef((viewProps, ref) => {
612
714
  style: normalStyle
613
715
  });
614
716
  const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef });
615
- const viewStyle = extendObject({}, innerStyle, layoutStyle);
616
- const transitionend = isFunction(catchtransitionend)
617
- ? catchtransitionend
618
- : isFunction(bindtransitionend)
619
- ? bindtransitionend
620
- : undefined;
717
+ const viewStyle = layoutStyle
718
+ ? extendObject({}, innerStyle, layoutStyle)
719
+ : innerStyle;
621
720
  const { enableStyleAnimation, animationStyle } = useAnimationHooks({
622
721
  layoutRef,
623
722
  animation,
624
723
  enableAnimation,
625
724
  style: viewStyle,
626
- transitionend
725
+ catchtransitionend,
726
+ bindtransitionend
627
727
  });
628
728
  if (__mpx_perf_framework__)
629
729
  perf.scopeEnd(idStyle);
@@ -635,15 +735,16 @@ const _View = forwardRef((viewProps, ref) => {
635
735
  ref: nodeRef,
636
736
  style: enableStyleAnimation ? [viewStyle, animationStyle] : viewStyle
637
737
  }), [
638
- 'animation',
639
738
  'hover-start-time',
640
739
  'hover-stay-time',
641
740
  'hover-style',
642
741
  'hover-class',
643
- 'enable-fast-image',
742
+ 'enable-background',
644
743
  'enable-animation',
645
- 'bindtransitionend',
646
- 'catchtransitionend'
744
+ 'enable-fast-image',
745
+ 'animation',
746
+ 'catchtransitionend',
747
+ 'bindtransitionend'
647
748
  ], {
648
749
  layoutRef
649
750
  });
@@ -18,6 +18,5 @@ interface WebViewProps {
18
18
  binderror?: (event: CommonCallbackEvent) => void;
19
19
  [x: string]: any;
20
20
  }
21
- type WebViewInstance = WebView<{}>;
22
- declare const _WebView: import("react").ForwardRefExoticComponent<Omit<WebViewProps, "ref"> & import("react").RefAttributes<HandlerRef<WebViewInstance, WebViewProps>>>;
21
+ declare const _WebView: import("react").ForwardRefExoticComponent<Omit<WebViewProps, "ref"> & import("react").RefAttributes<HandlerRef<WebView<{}>, WebViewProps>>>;
23
22
  export default _WebView;
@@ -9,6 +9,10 @@ import useNodesRef from './useNodesRef';
9
9
  import { getCurrentPage, useNavigation } from './utils';
10
10
  import { RouteContext } from './context';
11
11
  import { StyleSheet, View, Text } from 'react-native';
12
+ // react-native-webview@13.17+ 将 WebView 泛型默认值由 {} 改为 undefined,
13
+ // 导致 WebViewProps & undefined 塌成 never、JSX 直接使用时 props 全部不可赋值。
14
+ // 显式实参化为 WebView<object>(其 props 即 WebViewProps),保留类实例签名使 ref 仍解析为 WebView,兼容各版本声明。
15
+ const RNWebView = WebView;
12
16
  const styles = StyleSheet.create({
13
17
  loadErrorContext: {
14
18
  display: 'flex',
@@ -299,7 +303,7 @@ const _WebView = forwardRef((props, ref) => {
299
303
  <View style={styles.loadErrorText}><Text style={{ fontSize: 14, color: '#999999' }}>{currentErrorText.text}</Text></View>
300
304
  <View style={styles.loadErrorButton} onTouchEnd={_reload}><Text style={{ fontSize: 12, color: '#666666' }}>{currentErrorText.button}</Text></View>
301
305
  </View>)
302
- : (<WebView containerStyle={defaultWebViewStyle} source={{ uri: src }} ref={webViewRef} javaScriptEnabled={true} onNavigationStateChange={_changeUrl} onMessage={_message} injectedJavaScript={injectedJavaScript} onLoadProgress={_onLoadProgress} onLoadEnd={onLoadEnd} onHttpError={onHttpError} onError={onError} allowsBackForwardNavigationGestures={true}></WebView>)}
306
+ : (<RNWebView containerStyle={defaultWebViewStyle} source={{ uri: src }} ref={webViewRef} javaScriptEnabled={true} onNavigationStateChange={_changeUrl} onMessage={_message} injectedJavaScript={injectedJavaScript} onLoadProgress={_onLoadProgress} onLoadEnd={onLoadEnd} onHttpError={onHttpError} onError={onError} allowsBackForwardNavigationGestures={true}></RNWebView>)}
303
307
  </Portal>);
304
308
  });
305
309
  _WebView.displayName = 'MpxWebview';
@@ -0,0 +1,2 @@
1
+ declare const namedColorSet: Record<string, boolean>;
2
+ export default namedColorSet;