@mpxjs/webpack-plugin 2.9.59 → 2.9.64

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 (115) hide show
  1. package/lib/index.js +1 -3
  2. package/lib/platform/style/wx/index.js +344 -270
  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 +2 -0
  23. package/lib/react/processStyles.js +1 -0
  24. package/lib/react/processTemplate.js +2 -3
  25. package/lib/react/style-helper.js +12 -7
  26. package/lib/runtime/components/react/context.ts +40 -0
  27. package/lib/runtime/components/react/dist/context.js +8 -0
  28. package/lib/runtime/components/react/dist/getInnerListeners.js +34 -12
  29. package/lib/runtime/components/react/dist/mpx-button.jsx +88 -88
  30. package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +82 -0
  31. package/lib/runtime/components/react/dist/mpx-checkbox.jsx +139 -0
  32. package/lib/runtime/components/react/dist/mpx-form.jsx +61 -0
  33. package/lib/runtime/components/react/dist/mpx-icon.jsx +48 -0
  34. package/lib/runtime/components/react/dist/mpx-image/index.jsx +39 -43
  35. package/lib/runtime/components/react/dist/mpx-image/svg.jsx +3 -2
  36. package/lib/runtime/components/react/dist/mpx-input.jsx +63 -37
  37. package/lib/runtime/components/react/dist/mpx-label.jsx +55 -0
  38. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +41 -0
  39. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +346 -0
  40. package/lib/runtime/components/react/dist/mpx-navigator.jsx +35 -0
  41. package/lib/runtime/components/react/dist/mpx-picker/date.jsx +69 -0
  42. package/lib/runtime/components/react/dist/mpx-picker/index.jsx +138 -0
  43. package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +142 -0
  44. package/lib/runtime/components/react/dist/mpx-picker/region.jsx +94 -0
  45. package/lib/runtime/components/react/dist/mpx-picker/regionData.js +6099 -0
  46. package/lib/runtime/components/react/dist/mpx-picker/selector.jsx +76 -0
  47. package/lib/runtime/components/react/dist/mpx-picker/time.jsx +244 -0
  48. package/lib/runtime/components/react/dist/mpx-picker/type.js +1 -0
  49. package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +107 -0
  50. package/lib/runtime/components/react/dist/mpx-picker-view.jsx +162 -0
  51. package/lib/runtime/components/react/dist/mpx-radio-group.jsx +80 -0
  52. package/lib/runtime/components/react/dist/mpx-radio.jsx +154 -0
  53. package/lib/runtime/components/react/dist/mpx-root-portal.jsx +15 -0
  54. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +93 -70
  55. package/lib/runtime/components/react/dist/mpx-swiper/carouse.jsx +281 -157
  56. package/lib/runtime/components/react/dist/mpx-swiper/index.jsx +21 -11
  57. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +19 -11
  58. package/lib/runtime/components/react/dist/mpx-switch.jsx +79 -0
  59. package/lib/runtime/components/react/dist/mpx-text.jsx +21 -49
  60. package/lib/runtime/components/react/dist/mpx-textarea.jsx +2 -2
  61. package/lib/runtime/components/react/dist/mpx-view.jsx +451 -146
  62. package/lib/runtime/components/react/dist/mpx-web-view.jsx +17 -20
  63. package/lib/runtime/components/react/dist/parser.js +218 -0
  64. package/lib/runtime/components/react/dist/types/common.js +1 -0
  65. package/lib/runtime/components/react/dist/useNodesRef.js +3 -8
  66. package/lib/runtime/components/react/dist/utils.jsx +433 -0
  67. package/lib/runtime/components/react/getInnerListeners.ts +43 -21
  68. package/lib/runtime/components/react/mpx-button.tsx +129 -119
  69. package/lib/runtime/components/react/mpx-checkbox-group.tsx +152 -0
  70. package/lib/runtime/components/react/mpx-checkbox.tsx +234 -0
  71. package/lib/runtime/components/react/mpx-form.tsx +117 -0
  72. package/lib/runtime/components/react/mpx-icon.tsx +106 -0
  73. package/lib/runtime/components/react/mpx-image/index.tsx +62 -68
  74. package/lib/runtime/components/react/mpx-image/svg.tsx +7 -5
  75. package/lib/runtime/components/react/mpx-input.tsx +90 -42
  76. package/lib/runtime/components/react/mpx-label.tsx +110 -0
  77. package/lib/runtime/components/react/mpx-movable-area.tsx +81 -0
  78. package/lib/runtime/components/react/mpx-movable-view.tsx +424 -0
  79. package/lib/runtime/components/react/mpx-navigator.tsx +67 -0
  80. package/lib/runtime/components/react/mpx-picker/date.tsx +82 -0
  81. package/lib/runtime/components/react/mpx-picker/index.tsx +155 -0
  82. package/lib/runtime/components/react/mpx-picker/multiSelector.tsx +156 -0
  83. package/lib/runtime/components/react/mpx-picker/region.tsx +107 -0
  84. package/lib/runtime/components/react/mpx-picker/regionData.ts +6101 -0
  85. package/lib/runtime/components/react/mpx-picker/selector.tsx +91 -0
  86. package/lib/runtime/components/react/mpx-picker/time.tsx +270 -0
  87. package/lib/runtime/components/react/mpx-picker/type.ts +107 -0
  88. package/lib/runtime/components/react/mpx-picker-view-column.tsx +156 -0
  89. package/lib/runtime/components/react/mpx-picker-view.tsx +220 -0
  90. package/lib/runtime/components/react/mpx-radio-group.tsx +150 -0
  91. package/lib/runtime/components/react/mpx-radio.tsx +230 -0
  92. package/lib/runtime/components/react/mpx-root-portal.tsx +27 -0
  93. package/lib/runtime/components/react/mpx-scroll-view.tsx +184 -130
  94. package/lib/runtime/components/react/mpx-swiper/carouse.tsx +308 -183
  95. package/lib/runtime/components/react/mpx-swiper/index.tsx +27 -19
  96. package/lib/runtime/components/react/mpx-swiper/type.ts +23 -5
  97. package/lib/runtime/components/react/mpx-swiper-item.tsx +49 -14
  98. package/lib/runtime/components/react/mpx-switch.tsx +148 -0
  99. package/lib/runtime/components/react/mpx-text.tsx +53 -77
  100. package/lib/runtime/components/react/mpx-textarea.tsx +3 -3
  101. package/lib/runtime/components/react/mpx-view.tsx +576 -195
  102. package/lib/runtime/components/react/mpx-web-view.tsx +34 -39
  103. package/lib/runtime/components/react/parser.ts +245 -0
  104. package/lib/runtime/components/react/types/common.ts +12 -0
  105. package/lib/runtime/components/react/types/getInnerListeners.ts +2 -1
  106. package/lib/runtime/components/react/types/global.d.ts +17 -1
  107. package/lib/runtime/components/react/useNodesRef.ts +4 -10
  108. package/lib/runtime/components/react/utils.tsx +505 -0
  109. package/lib/runtime/optionProcessor.js +19 -17
  110. package/lib/template-compiler/compiler.js +84 -61
  111. package/lib/template-compiler/gen-node-react.js +7 -9
  112. package/lib/web/processStyles.js +2 -5
  113. package/package.json +8 -3
  114. package/lib/runtime/components/react/dist/utils.js +0 -80
  115. package/lib/runtime/components/react/utils.ts +0 -92
@@ -2,23 +2,49 @@ 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
- const unsupportedPropError = ({ prop, mode }) => {
13
- error(`Property [${prop}] is not supported in React Native ${mode} environment!`)
12
+ // var(xx)
13
+ const cssVariableExp = /var\(/
14
+ // calc(xx)
15
+ const calcExp = /calc\(/
16
+ // 不支持的属性提示
17
+ const unsupportedPropError = ({ prop, value, selector }, { mode }, isError = true) => {
18
+ const tips = isError ? error : warn
19
+ tips(`Property [${prop}] on ${selector} is not supported in ${mode} environment!`)
20
+ }
21
+ // prop 校验
22
+ const verifyProps = ({ prop, value, selector }, { mode }, isError = true) => {
23
+ prop = prop.trim()
24
+ if (unsupportedPropExp.test(prop) || unsupportedPropMode[mode].test(prop)) {
25
+ unsupportedPropError({ prop, value, selector }, { mode }, isError)
26
+ return false
27
+ }
28
+ return true
29
+ }
30
+ // 值类型
31
+ const ValueType = {
32
+ number: 'number',
33
+ color: 'color',
34
+ enum: 'enum'
14
35
  }
15
-
16
36
  // React 属性支持的枚举值
17
37
  const SUPPORTED_PROP_VAL_ARR = {
38
+ 'box-sizing': ['border-box'],
39
+ 'backface-visibility': ['visible', 'hidden'],
18
40
  overflow: ['visible', 'hidden', 'scroll'],
19
41
  'border-style': ['solid', 'dotted', 'dashed'],
42
+ 'object-fit': ['cover', 'contain', 'fill', 'scale-down'],
43
+ direction: ['inherit', 'ltr', 'rtl'],
20
44
  display: ['flex', 'none'],
21
- 'pointer-events': ['auto', 'none'],
45
+ 'flex-direction': ['row', 'row-reverse', 'column', 'column-reverse'],
46
+ 'flex-wrap': ['wrap', 'nowrap', 'wrap-reverse'],
47
+ 'pointer-events': ['auto', 'box-none', 'box-only', 'none'],
22
48
  'vertical-align': ['auto', 'top', 'bottom', 'center'],
23
49
  position: ['relative', 'absolute'],
24
50
  'font-variant': ['small-caps', 'oldstyle-nums', 'lining-nums', 'tabular-nums', 'proportional-nums'],
@@ -26,148 +52,149 @@ module.exports = function getSpec ({ warn, error }) {
26
52
  'font-style': ['normal', 'italic'],
27
53
  'font-weight': ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
28
54
  'text-decoration-line': ['none', 'underline', 'line-through', 'underline line-through'],
55
+ 'text-decoration-style': ['solid', 'double', 'dotted', 'dashed'],
29
56
  'text-transform': ['none', 'uppercase', 'lowercase', 'capitalize'],
30
57
  'user-select': ['auto', 'text', 'none', 'contain', 'all'],
31
- 'align-content': ['flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-around'],
58
+ 'align-content': ['flex-start', 'flex-end', 'center', 'stretch', 'space-between', 'space-around', 'space-evenly'],
32
59
  'align-items': ['flex-start', 'flex-end', 'center', 'stretch', 'baseline'],
33
60
  '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']
61
+ 'justify-content': ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly'],
62
+ 'background-size': ['contain', 'cover', 'auto', ValueType.number],
63
+ 'background-position': ['left', 'right', 'top', 'bottom', 'center', ValueType.number],
64
+ 'background-repeat': ['no-repeat'],
65
+ width: ['auto', ValueType.number],
66
+ height: ['auto', ValueType.number],
67
+ 'flex-basis': ['auto', ValueType.number],
68
+ margin: ['auto', ValueType.number],
69
+ 'margin-top': ['auto', ValueType.number],
70
+ 'margin-left': ['auto', ValueType.number],
71
+ 'margin-bottom': ['auto', ValueType.number],
72
+ 'margin-right': ['auto', ValueType.number],
73
+ 'margin-horizontal': ['auto', ValueType.number],
74
+ 'margin-vertical': ['auto', ValueType.number]
37
75
  }
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!`)
42
- }
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
76
+ // 获取值类型
77
+ const getValueType = (prop) => {
78
+ const propValueTypeRules = [
79
+ // 重要!!优先判断是不是枚举类型
80
+ [ValueType.enum, new RegExp('^(' + Object.keys(SUPPORTED_PROP_VAL_ARR).join('|') + ')$')],
81
+ [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)))$/],
82
+ [ValueType.color, /^(color|(.+-color))$/]
83
+ ]
84
+ for (const rule of propValueTypeRules) {
85
+ if (rule[1].test(prop)) return rule[0]
53
86
  }
54
87
  }
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
88
+ // 属性值校验
89
+ const verifyValues = ({ prop, value, selector }, isError = true) => {
90
+ prop = prop.trim()
91
+ value = value.trim()
92
+ const tips = isError ? error : warn
93
+ if (cssVariableExp.test(value) || calcExp.test(value)) return true
94
+ 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']
95
+ const valueExp = {
96
+ number: /^((-?\d+(\.\d+)?)(rpx|px|%|vw|vh)?|hairlineWidth)$/,
97
+ color: new RegExp(('^(' + namedColor.join('|') + ')$') + '|(^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$)|^(rgb|rgba|hsl|hsla|hwb)\\(.+\\)$')
98
+ }
99
+ const type = getValueType(prop)
100
+ const tipsType = (type) => {
101
+ const info = {
102
+ [ValueType.number]: '2rpx,10%,30rpx',
103
+ [ValueType.color]: 'rgb,rgba,hsl,hsla,hwb,named color,#000000',
104
+ [ValueType.enum]: `${SUPPORTED_PROP_VAL_ARR[prop]?.join(',')}`
76
105
  }
106
+ tips(`Value of ${prop} in ${selector} should be ${type}, eg ${info[type]}, received [${value}], please check again!`)
107
+ }
108
+ switch (type) {
77
109
  case ValueType.number: {
78
- const isNumber = numberRegExp.test(value)
79
- !isNumber && warn(`React Native property [${prop}] unit only supports [rpx,px,%]`)
80
- return isNumber
110
+ if (!valueExp.number.test(value)) {
111
+ tipsType(type)
112
+ return false
113
+ }
114
+ return true
115
+ }
116
+ case ValueType.color: {
117
+ if (!valueExp.color.test(value)) {
118
+ tipsType(type)
119
+ return false
120
+ }
121
+ return true
81
122
  }
82
- default:
123
+ case ValueType.enum: {
124
+ const isIn = SUPPORTED_PROP_VAL_ARR[prop].includes(value)
125
+ const isType = Object.keys(valueExp).some(item => valueExp[item].test(value) && SUPPORTED_PROP_VAL_ARR[prop].includes(ValueType[item]))
126
+ if (!isIn && !isType) {
127
+ tipsType(type)
128
+ return false
129
+ }
83
130
  return true
131
+ }
84
132
  }
133
+ return true
85
134
  }
86
- // 统一校验 value type 值类型
87
- const checkCommonValue = (valueType) => ({ prop, value }) => {
88
- verifyValues({ prop, value, valueType })
135
+ // prop & value 校验:过滤的不合法的属性和属性值
136
+ const verification = ({ prop, value, selector }, { mode }) => {
137
+ return verifyProps({ prop, value, selector }, { mode }) && verifyValues({ prop, value, selector }) && ({ prop, value })
89
138
  }
90
139
 
91
140
  // 简写转换规则
92
141
  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
- }
142
+ // 仅支持 offset-x | offset-y | blur-radius | color 排序
143
+ 'text-shadow': ['textShadowOffset.width', 'textShadowOffset.height', 'textShadowRadius', 'textShadowColor'],
144
+ // 仅支持 width | style | color 这种排序
145
+ border: ['borderWidth', 'borderStyle', 'borderColor'],
146
+ // 仅支持 width | style | color 这种排序
147
+ 'border-left': ['borderLeftWidth', 'borderLeftStyle', 'borderLeftColor'],
148
+ // 仅支持 width | style | color 这种排序
149
+ 'border-right': ['borderRightWidth', 'borderRightStyle', 'borderRightColor'],
150
+ // 仅支持 width | style | color 这种排序
151
+ 'border-top': ['borderTopWidth', 'borderTopStyle', 'borderTopColor'],
152
+ // 仅支持 width | style | color 这种排序
153
+ 'border-bottom': ['borderBottomWidth', 'borderBottomStyle', 'borderBottomColor'],
154
+ // 仅支持 offset-x | offset-y | blur-radius | color 排序
155
+ 'box-shadow': ['shadowOffset.width', 'shadowOffset.height', 'shadowRadius', 'shadowColor'],
156
+ // 仅支持 text-decoration-line text-decoration-style text-decoration-color 这种格式
157
+ 'text-decoration': ['textDecorationLine', 'textDecorationStyle', 'textDecorationColor'],
158
+ // flex-grow | flex-shrink | flex-basis
159
+ flex: ['flexGrow', 'flexShrink', 'flexBasis'],
160
+ // flex-flow: <'flex-direction'> or flex-flow: <'flex-direction'> and <'flex-wrap'>
161
+ 'flex-flow': ['flexDirection', 'flexWrap'],
162
+ 'border-radius': ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius']
150
163
  }
151
- const formatAbbreviation = ({ value, keyMap }) => {
164
+ const formatAbbreviation = ({ prop, value, selector }, { mode }) => {
165
+ const original = `${prop}:${value}`
166
+ const props = AbbreviationMap[prop]
152
167
  const values = value.trim().split(/\s(?![^()]*\))/)
153
168
  const cssMap = []
154
- const props = Object.getOwnPropertyNames(keyMap)
155
169
  let idx = 0
156
170
  let propsIdx = 0
157
- // 按值的个数循环赋值
158
- while (idx < values.length || propsIdx < props.length) {
171
+ const diff = values.length - props.length
172
+ while (idx < values.length) {
159
173
  const prop = props[propsIdx]
160
- const valueType = keyMap[prop]
161
- const dashProp = hump2dash(prop)
174
+ if (!prop) {
175
+ error(`Value of [${original}] in ${selector} has not enough props to assign, please check again!`)
176
+ break
177
+ }
162
178
  const value = values[idx]
163
- if (isIllegalValue({ prop: dashProp, value })) {
164
- // 过滤 rn prop 不支持 value
165
- unsupportedValueError({ prop: dashProp, value })
166
- idx += 1
167
- propsIdx += 1
168
- } else if (!verifyValues({ prop, value, valueType })) {
169
- // 校验 value 类型,类型不符则匹配下一个value
170
- idx += 1
179
+ const newProp = hump2dash(prop.replace(/\..+/, ''))
180
+ if (!verifyProps({ prop: newProp, value, selector }, { mode }, diff === 0)) {
181
+ // ios or android 不支持的 prop,跳过 prop
182
+ if (diff === 0) {
183
+ propsIdx++
184
+ idx++
185
+ } else {
186
+ propsIdx++
187
+ }
188
+ } else if (!verifyValues({ prop: newProp, value, selector }, diff === 0)) {
189
+ // 值不合法 跳过 value
190
+ if (diff === 0) {
191
+ propsIdx++
192
+ idx++
193
+ } else if (diff < 0) {
194
+ propsIdx++
195
+ } else {
196
+ idx++
197
+ }
171
198
  } else if (prop.includes('.')) {
172
199
  // 多个属性值的prop
173
200
  const [main, sub] = prop.split('.')
@@ -196,30 +223,10 @@ module.exports = function getSpec ({ warn, error }) {
196
223
  }
197
224
  return cssMap
198
225
  }
199
- const getAbbreviation = ({ prop, value }) => {
200
- const keyMap = AbbreviationMap[prop]
201
- return formatAbbreviation({ prop, value, keyMap })
202
- }
203
- // 简写过滤安卓不支持的类型
204
- const getAbbreviationAndroid = ({ prop, value }, { mode }) => {
205
- const cssMap = getAbbreviation({ prop, value })
206
- // android 不支持的 shadowOffset shadowOpacity shadowRadius textDecorationStyle 和 textDecorationStyle
207
- return cssMap.filter(({ prop }) => { // 不支持的 prop 提示 & 过滤不支持的 prop
208
- const dashProp = hump2dash(prop)
209
- if (unsupportedPropMode.android.test(dashProp)) {
210
- unsupportedPropError({ prop: dashProp, mode })
211
- return false
212
- }
213
- return true
214
- })
215
- }
216
226
 
217
- const formatMargins = ({ prop, value }) => {
227
+ // margin padding
228
+ const formatMargins = ({ prop, value, selector }) => {
218
229
  const values = value.trim().split(/\s(?![^()]*\))/)
219
- // validate
220
- for (let i = 0; i < values.length; i++) {
221
- verifyValues({ prop, value: values[i], valueType: ValueType.number })
222
- }
223
230
  // format
224
231
  let suffix = []
225
232
  switch (values.length) {
@@ -235,65 +242,49 @@ module.exports = function getSpec ({ warn, error }) {
235
242
  break
236
243
  }
237
244
  return values.map((value, index) => {
245
+ const newProp = `${prop}${suffix[index] || ''}`
246
+ // validate
247
+ verifyValues({ prop: hump2dash(newProp), value, selector }, false)
238
248
  return {
239
- prop: `${prop}${suffix[index] || ''}`,
249
+ prop: newProp,
240
250
  value: value
241
251
  }
242
252
  })
243
253
  }
244
254
 
245
- const formatLineHeight = ({ prop, value }) => {
246
- if (!verifyValues({ prop, value, valueType: ValueType.number })) return false
247
-
248
- return {
255
+ // line-height
256
+ const formatLineHeight = ({ prop, value, selector }) => {
257
+ return verifyValues({ prop, value, selector }) && ({
249
258
  prop,
250
259
  value: /^\s*-?\d+(\.\d+)?\s*$/.test(value) ? `${Math.round(value * 100)}%` : value
251
- }
252
- }
253
-
254
- const getFontVariant = ({ prop, value }) => {
255
- if (/^(font-variant-caps|font-variant-numeric|font-variant-east-asian|font-variant-alternates|font-variant-ligatures)$/.test(prop)) {
256
- error(`Property [${prop}] is not supported in React Native environment, please replace [font-variant]!`)
257
- }
258
- prop = 'font-variant'
259
- // 校验枚举值
260
- if (isIllegalValue({ prop, value })) {
261
- unsupportedValueError({ prop, value })
262
- return false
263
- }
264
- return {
265
- prop,
266
- value
267
- }
260
+ })
268
261
  }
269
262
 
270
- // background 相关属性的处理,仅支持以下属性,不支持其他背景相关的属性:/^((?!(-color)).)*background((?!(-color)).)*$/ 包含background且不包含background-color
271
- const checkBackgroundImage = ({ prop, value }, { mode }) => {
263
+ // background 相关属性的转换 Todo
264
+ // 仅支持以下属性,不支持其他背景相关的属性
265
+ // /^((?!(-color)).)*background((?!(-color)).)*$/ 包含background且不包含background-color
266
+ const checkBackgroundImage = ({ prop, value, selector }, { mode }) => {
272
267
  const bgPropMap = {
273
268
  image: 'background-image',
274
269
  color: 'background-color',
275
270
  size: 'background-size',
276
271
  repeat: 'background-repeat',
277
- // position: 'background-position',
272
+ position: 'background-position',
278
273
  all: 'background'
279
274
  }
280
275
  const urlExp = /url\(["']?(.*?)["']?\)/
276
+ const linerExp = /linear-gradient\(["']?(.*?)["']?\)/
281
277
  switch (prop) {
282
- case bgPropMap.color: {
283
- // background-color 背景色校验一下颜色值
284
- verifyValues({ prop, value, valueType: ValueType.color })
285
- return { prop, value }
286
- }
287
278
  case bgPropMap.image: {
288
279
  // background-image 仅支持背景图
289
280
  const imgUrl = value.match(urlExp)?.[0]
290
- if (/.*linear-gradient*./.test(value)) {
291
- error(`<linear-gradient()> is not supported in React Native ${mode} environment!`)
292
- }
281
+ const linerVal = value.match(linerExp)?.[0]
293
282
  if (imgUrl) {
294
283
  return { prop, value: imgUrl }
284
+ } else if (linerVal) {
285
+ return { prop, value: linerVal }
295
286
  } else {
296
- error(`[${prop}] only support value <url()>`)
287
+ error(`Value of ${prop} in ${selector} selector only support value <url()> or <linear-gradient()>, received ${value}, please check again!`)
297
288
  return false
298
289
  }
299
290
  }
@@ -303,28 +294,30 @@ module.exports = function getSpec ({ warn, error }) {
303
294
  // 支持一个值:这个值指定图片的宽度,图片的高度隐式的为 auto
304
295
  // 支持两个值:第一个值指定图片的宽度,第二个值指定图片的高度
305
296
  if (value.includes(',')) { // commas are not allowed in values
306
- error(`background size value[${value}] does not support commas in React Native ${mode} environment!`)
297
+ error(`Value of [${bgPropMap.size}] in ${selector} does not support commas, received [${value}], please check again!`)
307
298
  return false
308
299
  }
309
300
  const values = []
310
301
  value.trim().split(/\s(?![^()]*\))/).forEach(item => {
311
- if (numberRegExp.test(item) || !isIllegalValue({ prop, value: item })) {
302
+ if (verifyValues({ prop, value: item, selector })) {
312
303
  // 支持 number 值 / container cover auto 枚举
313
304
  values.push(item)
314
- } else {
315
- error(`background size value[${value}] does not support in React Native ${mode} environment!`)
316
305
  }
317
306
  })
318
307
  // value 无有效值时返回false
319
308
  return values.length === 0 ? false : { prop, value: values }
320
309
  }
321
- case bgPropMap.repeat: {
322
- // background-repeat 仅支持 no-repeat
323
- if (isIllegalValue({ prop, value })) {
324
- unsupportedValueError({ prop, value })
325
- return false
326
- }
327
- return { prop, value }
310
+ case bgPropMap.position: {
311
+ const values = []
312
+ value.trim().split(/\s(?![^()]*\))/).forEach(item => {
313
+ if (verifyValues({ prop, value: item, selector })) {
314
+ // 支持 number 值 / 枚举, center与50%等价
315
+ values.push(item === 'center' ? '50%' : item)
316
+ } else {
317
+ error(`Value of [${bgPropMap.size}] in ${selector} does not support commas, received [${value}], please check again!`)
318
+ }
319
+ })
320
+ return { prop, value: values }
328
321
  }
329
322
  case bgPropMap.all: {
330
323
  // background: 仅支持 background-image & background-color & background-repeat
@@ -332,43 +325,48 @@ module.exports = function getSpec ({ warn, error }) {
332
325
  const values = value.trim().split(/\s(?![^()]*\))/)
333
326
  values.forEach(item => {
334
327
  const url = item.match(urlExp)?.[0]
335
- if (/.*linear-gradient*./.test(item)) {
336
- error(`<linear-gradient()> is not supported in React Native ${mode} environment!`)
337
- } else if (url) {
328
+ const linerVal = item.match(linerExp)?.[0]
329
+ if (url) {
338
330
  bgMap.push({ prop: bgPropMap.image, value: url })
339
- } else if (/^(#[0-9a-f]{3}$|#[0-9a-f]{6}$|rgb|rgba)/i.test(item)) {
331
+ } else if (linerVal) {
332
+ bgMap.push({ prop: bgPropMap.image, value: linerVal })
333
+ } else if (verifyValues({ prop: bgPropMap.color, value: item }, false)) {
340
334
  bgMap.push({ prop: bgPropMap.color, value: item })
341
- } else if (SUPPORTED_PROP_VAL_ARR[bgPropMap.repeat].includes(item)) {
335
+ } else if (verifyValues({ prop: bgPropMap.repeat, value: item, selector }, false)) {
342
336
  bgMap.push({ prop: bgPropMap.repeat, value: item })
343
337
  }
344
- // else if (SUPPORTED_PROP_VAL_ARR[bgPropMap.size].includes(item)) {
345
- // bgMap.push({ prop: bgPropMap.size, value: item })
346
- // }
347
338
  })
348
339
  return bgMap.length ? bgMap : false
349
340
  }
350
341
  }
351
- unsupportedPropError({ prop, mode })
342
+ unsupportedPropError({ prop, value, selector }, { mode })
352
343
  return false
353
344
  }
354
345
 
355
- const getBorderRadius = ({ prop, value }) => {
346
+ // border-radius 缩写转换
347
+ const getBorderRadius = ({ prop, value, selector }, { mode }) => {
356
348
  const values = value.trim().split(/\s(?![^()]*\))/)
357
349
  if (values.length === 1) {
358
- verifyValues({ prop, value, valueType: ValueType.number })
350
+ verifyValues({ prop, value, selector }, false)
359
351
  return { prop, value }
360
352
  } else {
361
- return getAbbreviation({ prop, value })
353
+ if (values.length === 2) {
354
+ values.push(...values)
355
+ } else if (values.length === 3) {
356
+ values.push(values[1])
357
+ }
358
+ return formatAbbreviation({ prop, value: values.join(' ') }, { mode })
362
359
  }
363
360
  }
364
361
 
365
- const formatTransform = ({ prop, value }, { mode }) => {
362
+ // transform 转换
363
+ const formatTransform = ({ prop, value, selector }, { mode }) => {
366
364
  if (Array.isArray(value)) return { prop, value }
367
365
  const values = value.trim().split(/\s(?![^()]*\))/)
368
366
  const transform = []
369
367
  values.forEach(item => {
370
368
  const match = item.match(/([/\w]+)\(([^)]+)\)/)
371
- if (match.length >= 3) {
369
+ if (match && match.length >= 3) {
372
370
  let key = match[1]
373
371
  const val = match[2]
374
372
  switch (key) {
@@ -382,6 +380,7 @@ module.exports = function getSpec ({ warn, error }) {
382
380
  case 'rotate':
383
381
  case 'skewX':
384
382
  case 'skewY':
383
+ case 'perspective':
385
384
  // 单个值处理
386
385
  transform.push({ [key]: val })
387
386
  break
@@ -395,28 +394,28 @@ module.exports = function getSpec ({ warn, error }) {
395
394
  case 'rotate3d': // x y z angle
396
395
  case 'translate3d': // x y 支持 z不支持
397
396
  case 'scale3d': // x y 支持 z不支持
398
- {
399
- // 2 个以上的值处理
400
- key = key.replace('3d', '')
401
- const vals = val.split(',').splice(0, key === 'rotate' ? 4 : 3)
402
- const xyz = ['X', 'Y', 'Z']
403
- transform.push(...vals.map((v, index) => {
404
- if (key !== 'rotate' && index > 1) {
405
- unsupportedPropError({ prop: `${key}Z`, mode })
406
- }
407
- return { [`${key}${xyz[index] || ''}`]: v.trim() }
408
- }))
409
- break
410
- }
397
+ {
398
+ // 2 个以上的值处理
399
+ key = key.replace('3d', '')
400
+ const vals = val.split(',').splice(0, key === 'rotate' ? 4 : 3)
401
+ const xyz = ['X', 'Y', 'Z']
402
+ transform.push(...vals.map((v, index) => {
403
+ if (key !== 'rotate' && index > 1) {
404
+ unsupportedPropError({ prop: `${key}Z`, value, selector }, { mode })
405
+ }
406
+ return { [`${key}${xyz[index] || ''}`]: v.trim() }
407
+ }))
408
+ break
409
+ }
411
410
  case 'translateZ':
412
411
  case 'scaleZ':
413
412
  default:
414
413
  // 不支持的属性处理
415
- unsupportedPropError({ prop: key, mode })
414
+ unsupportedPropError({ prop, value, selector }, { mode })
416
415
  break
417
416
  }
418
417
  } else {
419
- error(`Property [${prop}] is invalid, please check the value!`)
418
+ error(`Property [${prop}] is invalid in ${selector}, received [${value}], please check again!`)
420
419
  }
421
420
  })
422
421
  return {
@@ -425,47 +424,113 @@ module.exports = function getSpec ({ warn, error }) {
425
424
  }
426
425
  }
427
426
 
428
- const spec = {
427
+ const isNumber = (value) => {
428
+ return !isNaN(+value)
429
+ }
430
+
431
+ const getIntegersFlex = ({ prop, value, selector }) => {
432
+ if (isNumber(value) && value >= 0) {
433
+ return { prop, value }
434
+ } else {
435
+ error(`Value of [${prop}] in ${selector} accepts any floating point value >= 0, received [${value}], please check again!`)
436
+ return false
437
+ }
438
+ }
439
+
440
+ const formatFlex = ({ prop, value, selector }) => {
441
+ let values = value.trim().split(/\s(?![^()]*\))/)
442
+ if (values.length > 3) {
443
+ error(`Value of [flex] in ${selector} supports up to three values, received [${value}], please check again!`)
444
+ values = values.splice(0, 3)
445
+ }
446
+ const cssMap = []
447
+ const lastOne = values[values.length - 1]
448
+ const isAuto = lastOne === 'auto'
449
+ // 枚举值 none initial
450
+ if (values.includes('initial') || values.includes('none')) {
451
+ // css flex: initial ===> flex: 0 1 ===> rn flex 0 1
452
+ // css flex: none ===> css flex: 0 0 ===> rn flex 0 0
453
+ if (values.length === 1) {
454
+ // 添加 basis 和 shrink
455
+ // value=initial 则 flexShrink=1,其他场景都是0
456
+ cssMap.push(...[{ prop: 'flexGrow', value: 0 }, { prop: 'flexShrink', value: +(values[0] === 'initial') }])
457
+ } else {
458
+ error(`Value of [${prop}] in ${selector} is invalid, When setting the value of flex to none or initial, only one value is supported.`)
459
+ }
460
+ return cssMap
461
+ }
462
+ // 最后一个值是flexBasis 的有效值(auto或者有单位百分比、px等)
463
+ // flex 0 1 auto flex auto flex 1 auto flex 1 30px flex 1 10% flex 1 1 auto
464
+ if (!isNumber(lastOne)) {
465
+ // 添加 grow 和 shrink
466
+ // 在设置 flex basis 有效值的场景下,如果没有设置 grow 和 shrink,则默认为1
467
+ // 单值 flex: 1 1 <flex-basis>
468
+ // 双值 flex: <flex-grow> 1 <flex-basis>
469
+ // 三值 flex: <flex-grow> <flex-shrink> <flex-basis>
470
+ for (let i = 0; i < 2; i++) {
471
+ const item = getIntegersFlex({ prop: AbbreviationMap[prop][i], value: isNumber(values[i]) ? values[i] : 1 })
472
+ item && cssMap.push(item)
473
+ }
474
+ if (!isAuto) {
475
+ // 有单位(百分比、px等) 的 value 赋值 flexBasis,auto 不处理
476
+ cssMap.push({
477
+ prop: 'flexBasis',
478
+ value: lastOne
479
+ })
480
+ }
481
+ return cssMap
482
+ }
483
+ // 纯数值:value 按flex-grow flex-shrink flex-basis 顺序赋值
484
+ // 兜底 shrink & basis
485
+ if (values.length === 1) {
486
+ values.push(...[1, 0])
487
+ } else if (values.length === 2) {
488
+ values.push(0)
489
+ }
490
+ // 循环赋值
491
+ for (let i = 0; i < values.length; i++) {
492
+ const item = getIntegersFlex({ prop: AbbreviationMap[prop][i], value: values[i] })
493
+ item && cssMap.push(item)
494
+ }
495
+ return cssMap
496
+ }
497
+
498
+ const formatFontFamily = ({ prop, value, selector }) => {
499
+ // 去掉引号 取逗号分隔后的第一个
500
+ const newVal = value.replace(/"|'/g, '').trim()
501
+ const values = newVal.split(',').filter(i => i)
502
+ if (!newVal || !values.length) {
503
+ error(`Value of [${prop}] is invalid in ${selector}, received [${value}], please check again!`)
504
+ return false
505
+ } else if (values.length > 1) {
506
+ warn(`Value of [${prop}] only supports one in ${selector}, received [${value}], and the first one is used by default.`)
507
+ }
508
+ return { prop, value: values[0].trim() }
509
+ }
510
+
511
+ const formatBoxShadow = ({ prop, value, selector }, { mode }) => {
512
+ value = value.trim()
513
+ if (value === 'none') {
514
+ return false
515
+ }
516
+ const cssMap = formatAbbreviation({ prop, value, selector }, { mode })
517
+ if (mode === 'android') return cssMap
518
+ // ios 阴影需要额外设置 shadowOpacity=1
519
+ cssMap.push({
520
+ prop: 'shadowOpacity',
521
+ value: 1
522
+ })
523
+ return cssMap
524
+ }
525
+
526
+ return {
429
527
  supportedModes: ['ios', 'android'],
430
528
  rules: [
431
529
  { // 背景相关属性的处理
432
- test: /^(background|background-image|background-color|background-size|background-repeat|background-position)$/,
530
+ test: /^(background|background-image|background-size|background-position)$/,
433
531
  ios: checkBackgroundImage,
434
532
  android: checkBackgroundImage
435
533
  },
436
- { // RN 不支持的 CSS property
437
- test: unsupportedPropExp,
438
- ios: delRule,
439
- android: delRule
440
- },
441
- { // React Native android 不支持的 CSS property
442
- test: unsupportedPropMode.android,
443
- android: delRule
444
- },
445
- { // React Native ios 不支持的 CSS property
446
- test: unsupportedPropMode.ios,
447
- ios: delRule
448
- },
449
- { // RN 支持的 CSS property value
450
- test: propValExp,
451
- ios: delRule,
452
- android: delRule
453
- },
454
- {
455
- test: 'box-shadow',
456
- ios: getAbbreviation,
457
- android: getAbbreviationAndroid
458
- },
459
- {
460
- test: 'text-decoration',
461
- ios: getAbbreviation,
462
- android: getAbbreviationAndroid
463
- },
464
- {
465
- test: /^(font-variant|font-variant-caps|font-variant-numeric|font-variant-east-asian|font-variant-alternates|font-variant-ligatures)$/,
466
- ios: getFontVariant,
467
- android: getFontVariant
468
- },
469
534
  {
470
535
  test: 'border-radius',
471
536
  ios: getBorderRadius,
@@ -476,12 +541,6 @@ module.exports = function getSpec ({ warn, error }) {
476
541
  ios: formatMargins,
477
542
  android: formatMargins
478
543
  },
479
- // 通用的简写格式匹配
480
- {
481
- test: new RegExp('^(' + Object.keys(AbbreviationMap).join('|') + ')$'),
482
- ios: getAbbreviation,
483
- android: getAbbreviation
484
- },
485
544
  { // line-height 换算
486
545
  test: 'line-height',
487
546
  ios: formatLineHeight,
@@ -492,18 +551,33 @@ module.exports = function getSpec ({ warn, error }) {
492
551
  ios: formatTransform,
493
552
  android: formatTransform
494
553
  },
495
- // 值类型校验放到最后
496
- { // color 颜色值校验 color xx-color 等
497
- test: /^(color|(.+-color))$/,
498
- ios: checkCommonValue(ValueType.color),
499
- android: checkCommonValue(ValueType.color)
554
+ {
555
+ test: 'flex',
556
+ ios: formatFlex,
557
+ android: formatFlex
558
+ },
559
+ {
560
+ test: 'font-family',
561
+ ios: formatFontFamily,
562
+ android: formatFontFamily
500
563
  },
501
- { // number 值校验 // width height xx-left xx-top 等
502
- test: /^((width|height)|(.+-(left|right|top|bottom|radius|spacing|size)))$/,
503
- ios: checkCommonValue(ValueType.number),
504
- android: checkCommonValue(ValueType.number)
564
+ {
565
+ test: 'box-shadow',
566
+ ios: formatBoxShadow,
567
+ android: formatBoxShadow
568
+ },
569
+ // 通用的简写格式匹配
570
+ {
571
+ test: new RegExp('^(' + Object.keys(AbbreviationMap).join('|') + ')$'),
572
+ ios: formatAbbreviation,
573
+ android: formatAbbreviation
574
+ },
575
+ // 属性&属性值校验
576
+ {
577
+ test: () => true,
578
+ ios: verification,
579
+ android: verification
505
580
  }
506
581
  ]
507
582
  }
508
- return spec
509
583
  }