@mpxjs/webpack-plugin 2.9.58 → 2.9.62

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 (109) hide show
  1. package/lib/config.js +1 -3
  2. package/lib/platform/style/wx/index.js +374 -239
  3. package/lib/platform/template/wx/component-config/checkbox-group.js +8 -0
  4. package/lib/platform/template/wx/component-config/checkbox.js +8 -0
  5. package/lib/platform/template/wx/component-config/cover-image.js +15 -0
  6. package/lib/platform/template/wx/component-config/cover-view.js +9 -0
  7. package/lib/platform/template/wx/component-config/form.js +13 -1
  8. package/lib/platform/template/wx/component-config/icon.js +8 -0
  9. package/lib/platform/template/wx/component-config/index.js +5 -1
  10. package/lib/platform/template/wx/component-config/label.js +15 -0
  11. package/lib/platform/template/wx/component-config/movable-area.js +18 -1
  12. package/lib/platform/template/wx/component-config/movable-view.js +18 -1
  13. package/lib/platform/template/wx/component-config/navigator.js +8 -0
  14. package/lib/platform/template/wx/component-config/picker-view-column.js +8 -0
  15. package/lib/platform/template/wx/component-config/picker-view.js +18 -2
  16. package/lib/platform/template/wx/component-config/picker.js +14 -1
  17. package/lib/platform/template/wx/component-config/radio-group.js +8 -0
  18. package/lib/platform/template/wx/component-config/radio.js +8 -0
  19. package/lib/platform/template/wx/component-config/root-portal.js +15 -0
  20. package/lib/platform/template/wx/component-config/switch.js +8 -0
  21. package/lib/platform/template/wx/component-config/unsupported.js +1 -3
  22. package/lib/react/processScript.js +7 -1
  23. package/lib/react/style-helper.js +10 -1
  24. package/lib/runtime/components/react/context.ts +38 -0
  25. package/lib/runtime/components/react/dist/context.js +7 -0
  26. package/lib/runtime/components/react/dist/getInnerListeners.js +24 -13
  27. package/lib/runtime/components/react/dist/mpx-button.jsx +67 -45
  28. package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +81 -0
  29. package/lib/runtime/components/react/dist/mpx-checkbox.jsx +152 -0
  30. package/lib/runtime/components/react/dist/mpx-form.jsx +59 -0
  31. package/lib/runtime/components/react/dist/mpx-icon.jsx +51 -0
  32. package/lib/runtime/components/react/dist/mpx-image/index.jsx +17 -22
  33. package/lib/runtime/components/react/dist/mpx-image/svg.jsx +0 -1
  34. package/lib/runtime/components/react/dist/mpx-input.jsx +38 -16
  35. package/lib/runtime/components/react/dist/mpx-label.jsx +63 -0
  36. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +46 -0
  37. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +346 -0
  38. package/lib/runtime/components/react/dist/mpx-navigator.jsx +35 -0
  39. package/lib/runtime/components/react/dist/mpx-picker/date.jsx +69 -0
  40. package/lib/runtime/components/react/dist/mpx-picker/index.jsx +138 -0
  41. package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +139 -0
  42. package/lib/runtime/components/react/dist/mpx-picker/region.jsx +90 -0
  43. package/lib/runtime/components/react/dist/mpx-picker/regionData.js +6099 -0
  44. package/lib/runtime/components/react/dist/mpx-picker/selector.jsx +76 -0
  45. package/lib/runtime/components/react/dist/mpx-picker/time.jsx +244 -0
  46. package/lib/runtime/components/react/dist/mpx-picker/type.js +1 -0
  47. package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +15 -0
  48. package/lib/runtime/components/react/dist/mpx-picker-view.jsx +68 -0
  49. package/lib/runtime/components/react/dist/mpx-radio-group.jsx +79 -0
  50. package/lib/runtime/components/react/dist/mpx-radio.jsx +169 -0
  51. package/lib/runtime/components/react/dist/mpx-root-portal.jsx +11 -0
  52. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +66 -50
  53. package/lib/runtime/components/react/dist/mpx-swiper/carouse.jsx +206 -147
  54. package/lib/runtime/components/react/dist/mpx-swiper/index.jsx +9 -7
  55. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +3 -3
  56. package/lib/runtime/components/react/dist/mpx-switch.jsx +76 -0
  57. package/lib/runtime/components/react/dist/mpx-text.jsx +7 -19
  58. package/lib/runtime/components/react/dist/mpx-textarea.jsx +1 -1
  59. package/lib/runtime/components/react/dist/mpx-view.jsx +326 -96
  60. package/lib/runtime/components/react/dist/mpx-web-view.jsx +9 -15
  61. package/lib/runtime/components/react/dist/types/common.js +1 -0
  62. package/lib/runtime/components/react/dist/useNodesRef.js +3 -8
  63. package/lib/runtime/components/react/dist/utils.js +83 -15
  64. package/lib/runtime/components/react/getInnerListeners.ts +27 -15
  65. package/lib/runtime/components/react/mpx-button.tsx +87 -67
  66. package/lib/runtime/components/react/mpx-checkbox-group.tsx +147 -0
  67. package/lib/runtime/components/react/mpx-checkbox.tsx +245 -0
  68. package/lib/runtime/components/react/mpx-form.tsx +89 -0
  69. package/lib/runtime/components/react/mpx-icon.tsx +103 -0
  70. package/lib/runtime/components/react/mpx-image/index.tsx +20 -32
  71. package/lib/runtime/components/react/mpx-image/svg.tsx +2 -2
  72. package/lib/runtime/components/react/mpx-input.tsx +54 -26
  73. package/lib/runtime/components/react/mpx-label.tsx +115 -0
  74. package/lib/runtime/components/react/mpx-movable-area.tsx +67 -0
  75. package/lib/runtime/components/react/mpx-movable-view.tsx +425 -0
  76. package/lib/runtime/components/react/mpx-navigator.tsx +67 -0
  77. package/lib/runtime/components/react/mpx-picker/date.tsx +83 -0
  78. package/lib/runtime/components/react/mpx-picker/index.tsx +155 -0
  79. package/lib/runtime/components/react/mpx-picker/multiSelector.tsx +153 -0
  80. package/lib/runtime/components/react/mpx-picker/region.tsx +104 -0
  81. package/lib/runtime/components/react/mpx-picker/regionData.ts +6101 -0
  82. package/lib/runtime/components/react/mpx-picker/selector.tsx +92 -0
  83. package/lib/runtime/components/react/mpx-picker/time.tsx +274 -0
  84. package/lib/runtime/components/react/mpx-picker/type.ts +107 -0
  85. package/lib/runtime/components/react/mpx-picker-view-column.tsx +28 -0
  86. package/lib/runtime/components/react/mpx-picker-view.tsx +104 -0
  87. package/lib/runtime/components/react/mpx-radio-group.tsx +147 -0
  88. package/lib/runtime/components/react/mpx-radio.tsx +246 -0
  89. package/lib/runtime/components/react/mpx-root-portal.tsx +25 -0
  90. package/lib/runtime/components/react/mpx-scroll-view.tsx +82 -58
  91. package/lib/runtime/components/react/mpx-swiper/carouse.tsx +203 -156
  92. package/lib/runtime/components/react/mpx-swiper/index.tsx +12 -13
  93. package/lib/runtime/components/react/mpx-swiper/type.ts +11 -4
  94. package/lib/runtime/components/react/mpx-swiper-item.tsx +5 -3
  95. package/lib/runtime/components/react/mpx-switch.tsx +127 -0
  96. package/lib/runtime/components/react/mpx-text.tsx +52 -68
  97. package/lib/runtime/components/react/mpx-textarea.tsx +2 -2
  98. package/lib/runtime/components/react/mpx-view.tsx +374 -141
  99. package/lib/runtime/components/react/mpx-web-view.tsx +24 -28
  100. package/lib/runtime/components/react/types/common.ts +12 -0
  101. package/lib/runtime/components/react/types/getInnerListeners.ts +2 -1
  102. package/lib/runtime/components/react/types/global.d.ts +4 -0
  103. package/lib/runtime/components/react/useNodesRef.ts +3 -8
  104. package/lib/runtime/components/react/utils.ts +94 -16
  105. package/lib/runtime/optionProcessor.js +19 -17
  106. package/lib/template-compiler/compiler.js +73 -44
  107. package/lib/template-compiler/gen-node-react.js +7 -7
  108. package/lib/utils/shallow-stringify.js +1 -1
  109. package/package.json +6 -3
@@ -2,23 +2,45 @@ const { hump2dash } = require('../../../utils/hump-dash')
2
2
 
3
3
  module.exports = function getSpec ({ warn, error }) {
4
4
  // React Native 双端都不支持的 CSS property
5
- const unsupportedPropExp = /^(box-sizing|white-space|text-overflow|animation|transition)$/
5
+ const unsupportedPropExp = /^(white-space|text-overflow|animation|transition|font-variant-caps|font-variant-numeric|font-variant-east-asian|font-variant-alternates|font-variant-ligatures|background-position|caret-color)$/
6
6
  const unsupportedPropMode = {
7
7
  // React Native ios 不支持的 CSS property
8
8
  ios: /^(vertical-align)$/,
9
9
  // React Native android 不支持的 CSS property
10
10
  android: /^(text-decoration-style|text-decoration-color|shadow-offset|shadow-opacity|shadow-radius)$/
11
11
  }
12
+ // 不支持的属性提示
12
13
  const unsupportedPropError = ({ prop, mode }) => {
13
14
  error(`Property [${prop}] is not supported in React Native ${mode} environment!`)
14
15
  }
15
-
16
+ // prop 校验
17
+ const verifyProps = ({ prop, value }, { mode }, isError = true) => {
18
+ prop = prop.trim()
19
+ const tips = isError ? error : warn
20
+ if (unsupportedPropExp.test(prop) || unsupportedPropMode[mode].test(prop)) {
21
+ tips(`Property [${prop}] is not supported in React Native ${mode} environment!`)
22
+ return false
23
+ }
24
+ return true
25
+ }
26
+ // 值类型
27
+ const ValueType = {
28
+ number: 'number',
29
+ color: 'color',
30
+ enum: 'enum'
31
+ }
16
32
  // React 属性支持的枚举值
17
33
  const SUPPORTED_PROP_VAL_ARR = {
34
+ 'box-sizing': ['border-box'],
35
+ 'backface-visibility': ['visible', 'hidden'],
18
36
  overflow: ['visible', 'hidden', 'scroll'],
19
37
  'border-style': ['solid', 'dotted', 'dashed'],
38
+ 'object-fit': ['cover', 'contain', 'fill', 'scale-down'],
39
+ direction: ['inherit', 'ltr', 'rtl'],
20
40
  display: ['flex', 'none'],
21
- 'pointer-events': ['auto', 'none'],
41
+ 'flex-direction': ['row', 'row-reverse', 'column', 'column-reverse'],
42
+ 'flex-wrap': ['wrap', 'nowrap', 'wrap-reverse'],
43
+ 'pointer-events': ['auto', 'box-none', 'box-only', 'none'],
22
44
  'vertical-align': ['auto', 'top', 'bottom', 'center'],
23
45
  position: ['relative', 'absolute'],
24
46
  'font-variant': ['small-caps', 'oldstyle-nums', 'lining-nums', 'tabular-nums', 'proportional-nums'],
@@ -26,144 +48,140 @@ module.exports = function getSpec ({ warn, error }) {
26
48
  'font-style': ['normal', 'italic'],
27
49
  'font-weight': ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
28
50
  'text-decoration-line': ['none', 'underline', 'line-through', 'underline line-through'],
51
+ 'text-decoration-style': ['solid', 'double', 'dotted', 'dashed'],
29
52
  'text-transform': ['none', 'uppercase', 'lowercase', 'capitalize'],
30
53
  'user-select': ['auto', 'text', 'none', 'contain', 'all'],
31
- 'align-content': ['flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-around'],
54
+ 'align-content': ['flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-around', 'space-evenly'],
32
55
  'align-items': ['flex-start', 'flex-end', 'center', 'stretch', 'baseline'],
33
56
  'align-self': ['auto', 'flex-start', 'flex-end', 'center', 'stretch', 'baseline'],
34
- 'justify-content': ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly', 'none'],
35
- 'background-size': ['contain', 'cover', 'auto'],
36
- 'background-repeat': ['no-repeat']
37
- }
38
- const propValExp = new RegExp('^(' + Object.keys(SUPPORTED_PROP_VAL_ARR).join('|') + ')$')
39
- const isIllegalValue = ({ prop, value }) => SUPPORTED_PROP_VAL_ARR[prop]?.length > 0 && !SUPPORTED_PROP_VAL_ARR[prop].includes(value)
40
- const unsupportedValueError = ({ prop, value }) => {
41
- error(`Property [${prop}] only support value [${SUPPORTED_PROP_VAL_ARR[prop]?.join(',')}] in React Native environment, the value [${value}] does not support!`)
57
+ 'justify-content': ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly'],
58
+ 'background-size': ['contain', 'cover', 'auto', ValueType.number],
59
+ 'background-position': ['left', 'right', 'top', 'bottom', 'center', ValueType.number],
60
+ 'background-repeat': ['no-repeat'],
61
+ width: ['auto', ValueType.number],
62
+ height: ['auto', ValueType.number],
63
+ 'flex-basis': ['auto', ValueType.number],
64
+ margin: ['auto', ValueType.number],
65
+ 'margin-top': ['auto', ValueType.number],
66
+ 'margin-left': ['auto', ValueType.number],
67
+ 'margin-bottom': ['auto', ValueType.number],
68
+ 'margin-right': ['auto', ValueType.number],
69
+ 'margin-horizontal': ['auto', ValueType.number],
70
+ 'margin-vertical': ['auto', ValueType.number]
42
71
  }
43
-
44
- // 过滤的不合法的属性
45
- const delRule = ({ prop, value }, { mode }) => {
46
- if (unsupportedPropExp.test(prop) || unsupportedPropMode[mode].test(prop)) {
47
- unsupportedPropError({ prop, mode })
48
- return false
49
- }
50
- if (isIllegalValue({ prop, value })) {
51
- unsupportedValueError({ prop, value })
52
- return false
72
+ // 获取值类型
73
+ const getValueType = (prop) => {
74
+ const propValueTypeRules = [
75
+ // 重要!!优先判断是不是枚举类型
76
+ [ValueType.enum, new RegExp('^(' + Object.keys(SUPPORTED_PROP_VAL_ARR).join('|') + ')$')],
77
+ [ValueType.number, /^((opacity|flex-grow|flex-shrink|gap|left|right|top|bottom)|(.+-(width|height|left|right|top|bottom|radius|spacing|size|gap|index|offset|opacity)))$/],
78
+ [ValueType.color, /^(color|(.+-color))$/]
79
+ ]
80
+ for (const rule of propValueTypeRules) {
81
+ if (rule[1].test(prop)) return rule[0]
53
82
  }
54
83
  }
55
-
56
- // color & number 值校验
57
- const ValueType = {
58
- number: 'number',
59
- color: 'color',
60
- default: 'default' // 不校验
61
- }
62
- // number 类型支持的单位(包含auto)
63
- const numberRegExp = /^\s*((-?\d+(\.\d+)?)(rpx|px|%)?)|(auto)\s*$/
64
- // RN 不支持的颜色格式
65
- const colorRegExp = /^\s*(lab|lch|oklab|oklch|color-mix|color|hwb|lch|light-dark).*$/
66
-
67
- const verifyValues = ({ prop, value, valueType }) => {
68
- // 校验 value 枚举 是否支持
69
- switch (valueType) {
70
- case ValueType.color: {
71
- const isNumber = numberRegExp.test(value)
72
- const isUnsupporttedColor = colorRegExp.test(value)
73
- isNumber && warn(`Property [${prop}] receives a valid color as value, not a number.`)
74
- isUnsupporttedColor && warn('React Native\'s supported color format does not contain [lab,lch,oklab,oklch,color-mix,color,hwb,lch,light-dark].')
75
- return !isNumber && !isUnsupporttedColor
76
- }
84
+ // 属性值校验
85
+ const verifyValues = ({ prop, value }, isError = true) => {
86
+ prop = prop.trim()
87
+ value = value.trim()
88
+ const type = getValueType(prop)
89
+ const namedColor = ['transparent', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
90
+ const valueExp = {
91
+ number: /^(-?\d+(\.\d+)?)(rpx|px|%)?$/,
92
+ color: new RegExp(('^(' + namedColor.join('|') + ')$') + '|(^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$)|^(rgb|rgba|hsl|hsla|hwb)\\(.+\\)$')
93
+ }
94
+ const tips = isError ? error : warn
95
+ switch (type) {
77
96
  case ValueType.number: {
78
- const isNumber = numberRegExp.test(value)
79
- !isNumber && warn(`React Native property [${prop}] unit only supports [rpx,px,%]`)
80
- return isNumber
97
+ if (!valueExp.number.test(value)) {
98
+ tips(`The value type of [${prop}] only supports [Number] in React Native environment, eg 10rpx, 10px, 10%, 10, please check again`)
99
+ return false
100
+ }
101
+ return true
81
102
  }
82
- default:
103
+ case ValueType.color: {
104
+ if (!valueExp.color.test(value)) {
105
+ tips(`The value type of [${prop}] only supports [Color] in React Native environment, eg #000, rgba(0,0,0,0), please check again`)
106
+ return false
107
+ }
83
108
  return true
109
+ }
110
+ case ValueType.enum: {
111
+ const isIn = SUPPORTED_PROP_VAL_ARR[prop].includes(value)
112
+ const isType = Object.keys(valueExp).some(item => valueExp[item].test(value) && SUPPORTED_PROP_VAL_ARR[prop].includes(ValueType[item]))
113
+ if (!isIn && !isType) {
114
+ tips(`Property [${prop}] only support value [${SUPPORTED_PROP_VAL_ARR[prop]?.join(',')}] in React Native environment, the value [${value}] does not support!`)
115
+ return false
116
+ }
117
+ return true
118
+ }
84
119
  }
120
+ return true
85
121
  }
86
- // 统一校验 value type 值类型
87
- const checkCommonValue = (valueType) => ({ prop, value }) => {
88
- verifyValues({ prop, value, valueType })
122
+ // prop & value 校验:过滤的不合法的属性和属性值
123
+ const verification = ({ prop, value }, { mode }) => {
124
+ return verifyProps({ prop, value }, { mode }) && verifyValues({ prop, value }) && ({ prop, value })
89
125
  }
90
126
 
91
127
  // 简写转换规则
92
128
  const AbbreviationMap = {
93
- 'text-shadow': { // 仅支持 offset-x | offset-y | blur-radius | color 排序
94
- 'textShadowOffset.width': ValueType.number,
95
- 'textShadowOffset.height': ValueType.number,
96
- textShadowRadius: ValueType.number,
97
- textShadowColor: ValueType.color
98
- },
99
- border: { // 仅支持 width | style | color 这种排序
100
- borderWidth: ValueType.number,
101
- borderStyle: ValueType.default,
102
- borderColor: ValueType.color
103
- },
104
- 'border-left': { // 仅支持 width | style | color 这种排序
105
- borderLeftWidth: ValueType.number,
106
- borderLeftStyle: ValueType.default,
107
- borderLeftColor: ValueType.color
108
- },
109
- 'border-right': { // 仅支持 width | style | color 这种排序
110
- borderRightWidth: ValueType.number,
111
- borderRightStyle: ValueType.default,
112
- borderRightColor: ValueType.color
113
- },
114
- 'border-top': { // 仅支持 width | style | color 这种排序
115
- borderTopWidth: ValueType.number,
116
- borderTopStyle: ValueType.default,
117
- borderTopColor: ValueType.color
118
- },
119
- 'border-bottom': { // 仅支持 width | style | color 这种排序
120
- borderBottomWidth: ValueType.number,
121
- borderBottomStyle: ValueType.default,
122
- borderBottomColor: ValueType.color
123
- },
124
- 'box-shadow': { // 仅支持 offset-x | offset-y | blur-radius | color 排序
125
- 'shadowOffset.width': ValueType.number,
126
- 'shadowOffset.height': ValueType.number,
127
- shadowRadius: ValueType.number,
128
- shadowColor: ValueType.color
129
- },
130
- 'text-decoration': { // 仅支持 text-decoration-line text-decoration-style text-decoration-color 这种格式
131
- textDecorationLine: ValueType.default,
132
- textDecorationStyle: ValueType.default,
133
- textDecorationColor: ValueType.color
134
- },
135
- flex: { // /* Three values: flex-grow | flex-shrink | flex-basis */
136
- flexGrow: ValueType.number,
137
- flexShrink: ValueType.number,
138
- flexBasis: ValueType.number
139
- },
140
- 'flex-flow': { // 仅支持 flex-flow: <'flex-direction'> or flex-flow: <'flex-direction'> and <'flex-wrap'>
141
- flexDirection: ValueType.default,
142
- flexWrap: ValueType.default
143
- },
144
- 'border-radius': {
145
- borderTopLeftRadius: ValueType.number,
146
- borderTopRightRadius: ValueType.number,
147
- borderBottomRightRadius: ValueType.number,
148
- borderBottomLeftRadius: ValueType.number
149
- }
129
+ // 仅支持 offset-x | offset-y | blur-radius | color 排序
130
+ 'text-shadow': ['textShadowOffset.width', 'textShadowOffset.height', 'textShadowRadius', 'textShadowColor'],
131
+ // 仅支持 width | style | color 这种排序
132
+ border: ['borderWidth', 'borderStyle', 'borderColor'],
133
+ // 仅支持 width | style | color 这种排序
134
+ 'border-left': ['borderLeftWidth', 'borderLeftStyle', 'borderLeftColor'],
135
+ // 仅支持 width | style | color 这种排序
136
+ 'border-right': ['borderRightWidth', 'borderRightStyle', 'borderRightColor'],
137
+ // 仅支持 width | style | color 这种排序
138
+ 'border-top': ['borderTopWidth', 'borderTopStyle', 'borderTopColor'],
139
+ // 仅支持 width | style | color 这种排序
140
+ 'border-bottom': ['borderBottomWidth', 'borderBottomStyle', 'borderBottomColor'],
141
+ // 仅支持 offset-x | offset-y | blur-radius | color 排序
142
+ 'box-shadow': ['shadowOffset.width', 'shadowOffset.height', 'shadowRadius', 'shadowColor'],
143
+ // 仅支持 text-decoration-line text-decoration-style text-decoration-color 这种格式
144
+ 'text-decoration': ['textDecorationLine', 'textDecorationStyle', 'textDecorationColor'],
145
+ // flex-grow | flex-shrink | flex-basis
146
+ flex: ['flexGrow', 'flexShrink', 'flexBasis'],
147
+ // flex-flow: <'flex-direction'> or flex-flow: <'flex-direction'> and <'flex-wrap'>
148
+ 'flex-flow': ['flexDirection', 'flexWrap'],
149
+ 'border-radius': ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius']
150
150
  }
151
- const formatAbbreviation = ({ value, keyMap }) => {
151
+ const formatAbbreviation = ({ prop, value }, { mode }) => {
152
+ const original = `${prop}:${value}`
153
+ const props = AbbreviationMap[prop]
152
154
  const values = value.trim().split(/\s(?![^()]*\))/)
153
155
  const cssMap = []
154
- const props = Object.getOwnPropertyNames(keyMap)
155
156
  let idx = 0
156
- // 按值的个数循环赋值
157
- while (idx < values.length && idx < props.length) {
158
- const prop = props[idx]
159
- const valueType = keyMap[prop]
160
- const dashProp = hump2dash(prop)
161
- // 校验 value 类型
162
- verifyValues({ prop, value: values[idx], valueType })
157
+ let propsIdx = 0
158
+ const diff = values.length - props.length
159
+ while (idx < values.length) {
160
+ const prop = props[propsIdx]
161
+ if (!prop) {
162
+ error(`the value of [${original}] has not enough props to assign in React Native environment, please check again`)
163
+ break
164
+ }
163
165
  const value = values[idx]
164
- if (isIllegalValue({ prop: dashProp, value })) {
165
- // 过滤不支持 value
166
- unsupportedValueError({ prop: dashProp, value })
166
+ const newProp = hump2dash(prop.replace(/\..+/, ''))
167
+ if (!verifyProps({ prop: newProp, value }, { mode }, diff === 0)) {
168
+ // ios or android 不支持的 prop,跳过 prop
169
+ if (diff === 0) {
170
+ propsIdx++
171
+ idx++
172
+ } else {
173
+ propsIdx++
174
+ }
175
+ } else if (!verifyValues({ prop: newProp, value }, diff === 0)) {
176
+ // 值不合法 跳过 value
177
+ if (diff === 0) {
178
+ propsIdx++
179
+ idx++
180
+ } else if (diff < 0) {
181
+ propsIdx++
182
+ } else {
183
+ idx++
184
+ }
167
185
  } else if (prop.includes('.')) {
168
186
  // 多个属性值的prop
169
187
  const [main, sub] = prop.split('.')
@@ -178,41 +196,24 @@ module.exports = function getSpec ({ warn, error }) {
178
196
  }
179
197
  })
180
198
  }
199
+ idx += 1
200
+ propsIdx += 1
181
201
  } else {
182
202
  // 单个值的属性
183
203
  cssMap.push({
184
204
  prop,
185
205
  value
186
206
  })
207
+ idx += 1
208
+ propsIdx += 1
187
209
  }
188
- idx += 1
189
210
  }
190
211
  return cssMap
191
212
  }
192
- const getAbbreviation = ({ prop, value }) => {
193
- const keyMap = AbbreviationMap[prop]
194
- return formatAbbreviation({ prop, value, keyMap })
195
- }
196
- // 简写过滤安卓不支持的类型
197
- const getAbbreviationAndroid = ({ prop, value }, { mode }) => {
198
- const cssMap = getAbbreviation({ prop, value })
199
- // android 不支持的 shadowOffset shadowOpacity shadowRadius textDecorationStyle 和 textDecorationStyle
200
- return cssMap.filter(({ prop }) => { // 不支持的 prop 提示 & 过滤不支持的 prop
201
- const dashProp = hump2dash(prop)
202
- if (unsupportedPropMode.android.test(dashProp)) {
203
- unsupportedPropError({ prop: dashProp, mode })
204
- return false
205
- }
206
- return true
207
- })
208
- }
209
213
 
214
+ // margin padding
210
215
  const formatMargins = ({ prop, value }) => {
211
216
  const values = value.trim().split(/\s(?![^()]*\))/)
212
- // validate
213
- for (let i = 0; i < values.length; i++) {
214
- verifyValues({ prop, value: values[i], valueType: ValueType.number })
215
- }
216
217
  // format
217
218
  let suffix = []
218
219
  switch (values.length) {
@@ -228,55 +229,38 @@ module.exports = function getSpec ({ warn, error }) {
228
229
  break
229
230
  }
230
231
  return values.map((value, index) => {
232
+ const newProp = `${prop}${suffix[index] || ''}`
233
+ // validate
234
+ verifyValues({ prop: hump2dash(newProp), value }, false)
231
235
  return {
232
- prop: `${prop}${suffix[index] || ''}`,
236
+ prop: newProp,
233
237
  value: value
234
238
  }
235
239
  })
236
240
  }
237
241
 
242
+ // line-height
238
243
  const formatLineHeight = ({ prop, value }) => {
239
- if (!verifyValues({ prop, value, valueType: ValueType.number })) return false
240
-
241
- return {
244
+ return verifyValues({ prop, value }) && ({
242
245
  prop,
243
246
  value: /^\s*-?\d+(\.\d+)?\s*$/.test(value) ? `${Math.round(value * 100)}%` : value
244
- }
245
- }
246
-
247
- const getFontVariant = ({ prop, value }) => {
248
- if (/^(font-variant-caps|font-variant-numeric|font-variant-east-asian|font-variant-alternates|font-variant-ligatures)$/.test(prop)) {
249
- error(`Property [${prop}] is not supported in React Native environment, please replace [font-variant]!`)
250
- }
251
- prop = 'font-variant'
252
- // 校验枚举值
253
- if (isIllegalValue({ prop, value })) {
254
- unsupportedValueError({ prop, value })
255
- return false
256
- }
257
- return {
258
- prop,
259
- value
260
- }
247
+ })
261
248
  }
262
249
 
263
- // background 相关属性的处理,仅支持以下属性,不支持其他背景相关的属性:/^((?!(-color)).)*background((?!(-color)).)*$/ 包含background且不包含background-color
250
+ // background 相关属性的转换 Todo
251
+ // 仅支持以下属性,不支持其他背景相关的属性
252
+ // /^((?!(-color)).)*background((?!(-color)).)*$/ 包含background且不包含background-color
264
253
  const checkBackgroundImage = ({ prop, value }, { mode }) => {
265
254
  const bgPropMap = {
266
255
  image: 'background-image',
267
256
  color: 'background-color',
268
257
  size: 'background-size',
269
258
  repeat: 'background-repeat',
270
- // position: 'background-position',
259
+ position: 'background-position',
271
260
  all: 'background'
272
261
  }
273
262
  const urlExp = /url\(["']?(.*?)["']?\)/
274
263
  switch (prop) {
275
- case bgPropMap.color: {
276
- // background-color 背景色校验一下颜色值
277
- verifyValues({ prop, value, valueType: ValueType.color })
278
- return { prop, value }
279
- }
280
264
  case bgPropMap.image: {
281
265
  // background-image 仅支持背景图
282
266
  const imgUrl = value.match(urlExp)?.[0]
@@ -301,23 +285,26 @@ module.exports = function getSpec ({ warn, error }) {
301
285
  }
302
286
  const values = []
303
287
  value.trim().split(/\s(?![^()]*\))/).forEach(item => {
304
- if (numberRegExp.test(item) || !isIllegalValue({ prop, value: item })) {
288
+ if (verifyValues({ prop, value: item })) {
305
289
  // 支持 number 值 / container cover auto 枚举
306
290
  values.push(item)
307
- } else {
308
- error(`background size value[${value}] does not support in React Native ${mode} environment!`)
309
291
  }
310
292
  })
311
293
  // value 无有效值时返回false
312
294
  return values.length === 0 ? false : { prop, value: values }
313
295
  }
314
- case bgPropMap.repeat: {
315
- // background-repeat 仅支持 no-repeat
316
- if (isIllegalValue({ prop, value })) {
317
- unsupportedValueError({ prop, value })
318
- return false
319
- }
320
- return { prop, value }
296
+ case bgPropMap.position: {
297
+ const values = []
298
+ value.trim().split(/\s(?![^()]*\))/).forEach(item => {
299
+ if (verifyValues({ prop, value: item })) {
300
+ // 支持 number 值 / 枚举, center与50%等价
301
+ values.push(item === 'center' ? '50%' : item)
302
+ } else {
303
+ error(`background position value[${value}] does not support in React Native ${mode} environment!`)
304
+ }
305
+ })
306
+
307
+ return { prop, value: values }
321
308
  }
322
309
  case bgPropMap.all: {
323
310
  // background: 仅支持 background-image & background-color & background-repeat
@@ -329,14 +316,11 @@ module.exports = function getSpec ({ warn, error }) {
329
316
  error(`<linear-gradient()> is not supported in React Native ${mode} environment!`)
330
317
  } else if (url) {
331
318
  bgMap.push({ prop: bgPropMap.image, value: url })
332
- } else if (/^(#[0-9a-f]{3}$|#[0-9a-f]{6}$|rgb|rgba)/i.test(item)) {
319
+ } else if (verifyValues({ prop: bgPropMap.color, value: item }, false)) {
333
320
  bgMap.push({ prop: bgPropMap.color, value: item })
334
- } else if (SUPPORTED_PROP_VAL_ARR[bgPropMap.repeat].includes(item)) {
321
+ } else if (verifyValues({ prop: bgPropMap.repeat, value: item }, false)) {
335
322
  bgMap.push({ prop: bgPropMap.repeat, value: item })
336
323
  }
337
- // else if (SUPPORTED_PROP_VAL_ARR[bgPropMap.size].includes(item)) {
338
- // bgMap.push({ prop: bgPropMap.size, value: item })
339
- // }
340
324
  })
341
325
  return bgMap.length ? bgMap : false
342
326
  }
@@ -345,57 +329,194 @@ module.exports = function getSpec ({ warn, error }) {
345
329
  return false
346
330
  }
347
331
 
348
- const getBorderRadius = ({ prop, value }) => {
332
+ // border-radius 缩写转换
333
+ const getBorderRadius = ({ prop, value }, { mode }) => {
349
334
  const values = value.trim().split(/\s(?![^()]*\))/)
350
335
  if (values.length === 1) {
351
- verifyValues({ prop, value, valueType: ValueType.number })
336
+ verifyValues({ prop, value }, false)
337
+ return { prop, value }
338
+ } else {
339
+ if (values.length === 2) {
340
+ values.push(...values)
341
+ } else if (values.length === 3) {
342
+ values.push(values[1])
343
+ }
344
+ return formatAbbreviation({ prop, value: values.join(' ') }, { mode })
345
+ }
346
+ }
347
+
348
+ // transform 转换
349
+ const formatTransform = ({ prop, value }, { mode }) => {
350
+ if (Array.isArray(value)) return { prop, value }
351
+ const values = value.trim().split(/\s(?![^()]*\))/)
352
+ const transform = []
353
+ values.forEach(item => {
354
+ const match = item.match(/([/\w]+)\(([^)]+)\)/)
355
+ if (match && match.length >= 3) {
356
+ let key = match[1]
357
+ const val = match[2]
358
+ switch (key) {
359
+ case 'translateX':
360
+ case 'translateY':
361
+ case 'scaleX':
362
+ case 'scaleY':
363
+ case 'rotateX':
364
+ case 'rotateY':
365
+ case 'rotateZ':
366
+ case 'rotate':
367
+ case 'skewX':
368
+ case 'skewY':
369
+ case 'perspective':
370
+ // 单个值处理
371
+ transform.push({ [key]: val })
372
+ break
373
+ case 'matrix':
374
+ case 'matrix3d':
375
+ transform.push({ [key]: val.split(',').map(val => +val) })
376
+ break
377
+ case 'translate':
378
+ case 'scale':
379
+ case 'skew':
380
+ case 'rotate3d': // x y z angle
381
+ case 'translate3d': // x y 支持 z不支持
382
+ case 'scale3d': // x y 支持 z不支持
383
+ {
384
+ // 2 个以上的值处理
385
+ key = key.replace('3d', '')
386
+ const vals = val.split(',').splice(0, key === 'rotate' ? 4 : 3)
387
+ const xyz = ['X', 'Y', 'Z']
388
+ transform.push(...vals.map((v, index) => {
389
+ if (key !== 'rotate' && index > 1) {
390
+ unsupportedPropError({ prop: `${key}Z`, mode })
391
+ }
392
+ return { [`${key}${xyz[index] || ''}`]: v.trim() }
393
+ }))
394
+ break
395
+ }
396
+ case 'translateZ':
397
+ case 'scaleZ':
398
+ default:
399
+ // 不支持的属性处理
400
+ unsupportedPropError({ prop: key, mode })
401
+ break
402
+ }
403
+ } else {
404
+ error(`Property [${prop}] is invalid, please check the value!`)
405
+ }
406
+ })
407
+ return {
408
+ prop,
409
+ value: transform
410
+ }
411
+ }
412
+
413
+ const isNumber = (value) => {
414
+ return !isNaN(+value)
415
+ }
416
+
417
+ const getIntegersFlex = ({ prop, value }) => {
418
+ if (isNumber(value) && value >= 0) {
352
419
  return { prop, value }
353
420
  } else {
354
- return getAbbreviation({ prop, value })
421
+ error(`The value of ${prop} accepts any floating point value >= 0.`)
422
+ return false
355
423
  }
356
424
  }
357
425
 
358
- const spec = {
426
+ const formatFlex = ({ prop, value }, { mode }) => {
427
+ let values = value.trim().split(/\s(?![^()]*\))/)
428
+ if (values.length > 3) {
429
+ error('The value of prop [flex] supports up to three values')
430
+ values = values.splice(0, 3)
431
+ }
432
+ const cssMap = []
433
+ const lastOne = values[values.length - 1]
434
+ const isAuto = lastOne === 'auto'
435
+ // 枚举值 none initial
436
+ if (values.includes('initial') || values.includes('none')) {
437
+ // css flex: initial ===> flex: 0 1 ===> rn flex 0 1
438
+ // css flex: none ===> css flex: 0 0 ===> rn flex 0 0
439
+ if (values.length === 1) {
440
+ // 添加 basis 和 shrink
441
+ // value=initial 则 flexShrink=1,其他场景都是0
442
+ cssMap.push(...[{ prop: 'flexGrow', value: 0 }, { prop: 'flexShrink', value: +(values[0] === 'initial') }])
443
+ } else {
444
+ error('When setting the value of flex to none or initial, only one value is supported.')
445
+ }
446
+ return cssMap
447
+ }
448
+ // 最后一个值是flexBasis 的有效值(auto或者有单位百分比、px等)
449
+ // flex 0 1 auto flex auto flex 1 auto flex 1 30px flex 1 10% flex 1 1 auto
450
+ if (!isNumber(lastOne)) {
451
+ // 添加 grow 和 shrink
452
+ // 在设置 flex basis 有效值的场景下,如果没有设置 grow 和 shrink,则默认为1
453
+ // 单值 flex: 1 1 <flex-basis>
454
+ // 双值 flex: <flex-grow> 1 <flex-basis>
455
+ // 三值 flex: <flex-grow> <flex-shrink> <flex-basis>
456
+ for (let i = 0; i < 2; i++) {
457
+ const item = getIntegersFlex({ prop: AbbreviationMap[prop][i], value: isNumber(values[i]) ? values[i] : 1 })
458
+ item && cssMap.push(item)
459
+ }
460
+ if (!isAuto) {
461
+ // 有单位(百分比、px等) 的 value 赋值 flexBasis,auto 不处理
462
+ cssMap.push({
463
+ prop: 'flexBasis',
464
+ value: lastOne
465
+ })
466
+ }
467
+ return cssMap
468
+ }
469
+ // 纯数值:value 按flex-grow flex-shrink flex-basis 顺序赋值
470
+ // 兜底 shrink & basis
471
+ if (values.length === 1) {
472
+ values.push(...[1, 0])
473
+ } else if (values.length === 2) {
474
+ values.push(0)
475
+ }
476
+ // 循环赋值
477
+ for (let i = 0; i < values.length; i++) {
478
+ const item = getIntegersFlex({ prop: AbbreviationMap[prop][i], value: values[i] })
479
+ item && cssMap.push(item)
480
+ }
481
+ return cssMap
482
+ }
483
+
484
+ const formatFontFamily = ({ prop, value }) => {
485
+ // 去掉引号 取逗号分隔后的第一个
486
+ const newVal = value.replace(/"|'/g, '').trim()
487
+ const values = newVal.split(',').filter(i => i)
488
+ if (!newVal || !values.length) {
489
+ error(`The value of prop [${prop}: ${value}] is invaild, please check again`)
490
+ return false
491
+ } else if (values.length > 1) {
492
+ warn(`The value of prop [${prop}] only supports one, and the first one is used by default`)
493
+ }
494
+ return { prop, value: values[0].trim() }
495
+ }
496
+
497
+ const formatBoxShadow = ({ prop, value }, { mode }) => {
498
+ value = value.trim()
499
+ if (value === 'none') {
500
+ return false
501
+ }
502
+ const cssMap = formatAbbreviation({ prop, value }, { mode })
503
+ if (mode === 'android') return cssMap
504
+ // ios 阴影需要额外设置 shadowOpacity=1
505
+ cssMap.push({
506
+ prop: 'shadowOpacity',
507
+ value: 1
508
+ })
509
+ return cssMap
510
+ }
511
+
512
+ return {
359
513
  supportedModes: ['ios', 'android'],
360
514
  rules: [
361
515
  { // 背景相关属性的处理
362
- test: /^(background|background-image|background-color|background-size|background-repeat|background-position)$/,
516
+ test: /^(background|background-image|background-size|background-position)$/,
363
517
  ios: checkBackgroundImage,
364
518
  android: checkBackgroundImage
365
519
  },
366
- { // RN 不支持的 CSS property
367
- test: unsupportedPropExp,
368
- ios: delRule,
369
- android: delRule
370
- },
371
- { // React Native android 不支持的 CSS property
372
- test: unsupportedPropMode.android,
373
- android: delRule
374
- },
375
- { // React Native ios 不支持的 CSS property
376
- test: unsupportedPropMode.ios,
377
- ios: delRule
378
- },
379
- { // RN 支持的 CSS property value
380
- test: propValExp,
381
- ios: delRule,
382
- android: delRule
383
- },
384
- {
385
- test: 'box-shadow',
386
- ios: getAbbreviation,
387
- android: getAbbreviationAndroid
388
- },
389
- {
390
- test: 'text-decoration',
391
- ios: getAbbreviation,
392
- android: getAbbreviationAndroid
393
- },
394
- {
395
- test: /^(font-variant|font-variant-caps|font-variant-numeric|font-variant-east-asian|font-variant-alternates|font-variant-ligatures)$/,
396
- ios: getFontVariant,
397
- android: getFontVariant
398
- },
399
520
  {
400
521
  test: 'border-radius',
401
522
  ios: getBorderRadius,
@@ -406,29 +527,43 @@ module.exports = function getSpec ({ warn, error }) {
406
527
  ios: formatMargins,
407
528
  android: formatMargins
408
529
  },
409
- // 通用的简写格式匹配
410
- {
411
- test: new RegExp('^(' + Object.keys(AbbreviationMap).join('|') + ')$'),
412
- ios: getAbbreviation,
413
- android: getAbbreviation
414
- },
415
530
  { // line-height 换算
416
531
  test: 'line-height',
417
532
  ios: formatLineHeight,
418
533
  android: formatLineHeight
419
534
  },
420
- // 值类型校验放到最后
421
- { // color 颜色值校验 color xx-color 等
422
- test: /^(color|(.+-color))$/,
423
- ios: checkCommonValue(ValueType.color),
424
- android: checkCommonValue(ValueType.color)
535
+ {
536
+ test: 'transform',
537
+ ios: formatTransform,
538
+ android: formatTransform
539
+ },
540
+ {
541
+ test: 'flex',
542
+ ios: formatFlex,
543
+ android: formatFlex
544
+ },
545
+ {
546
+ test: 'font-family',
547
+ ios: formatFontFamily,
548
+ android: formatFontFamily
425
549
  },
426
- { // number 值校验 // width height xx-left xx-top 等
427
- test: /^((width|height)|(.+-(left|right|top|bottom|radius|spacing|size)))$/,
428
- ios: checkCommonValue(ValueType.number),
429
- android: checkCommonValue(ValueType.number)
550
+ {
551
+ test: 'box-shadow',
552
+ ios: formatBoxShadow,
553
+ android: formatBoxShadow
554
+ },
555
+ // 通用的简写格式匹配
556
+ {
557
+ test: new RegExp('^(' + Object.keys(AbbreviationMap).join('|') + ')$'),
558
+ ios: formatAbbreviation,
559
+ android: formatAbbreviation
560
+ },
561
+ // 属性&属性值校验
562
+ {
563
+ test: () => true,
564
+ ios: verification,
565
+ android: verification
430
566
  }
431
567
  ]
432
568
  }
433
- return spec
434
569
  }