@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.
- package/lib/index.js +1 -4
- package/lib/platform/style/wx/index.js +395 -104
- package/lib/react/style-helper.js +6 -6
- package/lib/resolver/AddEnvPlugin.js +1 -1
- package/lib/resolver/AddModePlugin.js +1 -1
- package/lib/runtime/components/react/animationHooks/index.ts +7 -3
- package/lib/runtime/components/react/dist/animationHooks/index.d.ts +0 -2
- package/lib/runtime/components/react/dist/animationHooks/index.js +6 -1
- package/lib/runtime/components/react/dist/event.config.d.ts +2 -6
- package/lib/runtime/components/react/dist/event.config.js +28 -24
- package/lib/runtime/components/react/dist/getInnerListeners.d.ts +1 -1
- package/lib/runtime/components/react/dist/getInnerListeners.js +74 -56
- package/lib/runtime/components/react/dist/mpx-button.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-button.jsx +16 -9
- package/lib/runtime/components/react/dist/mpx-camera.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-camera.jsx +1 -2
- package/lib/runtime/components/react/dist/mpx-canvas/index.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-canvas/index.jsx +1 -2
- package/lib/runtime/components/react/dist/mpx-canvas/utils.d.ts +1 -3
- package/lib/runtime/components/react/dist/mpx-checkbox-group.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +3 -4
- package/lib/runtime/components/react/dist/mpx-checkbox.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-checkbox.jsx +8 -9
- package/lib/runtime/components/react/dist/mpx-form.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-form.jsx +5 -5
- package/lib/runtime/components/react/dist/mpx-icon/index.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-icon/index.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-image.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-image.jsx +59 -43
- package/lib/runtime/components/react/dist/mpx-input.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-input.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-label.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-label.jsx +5 -6
- package/lib/runtime/components/react/dist/mpx-movable-area.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-movable-area.jsx +3 -3
- package/lib/runtime/components/react/dist/mpx-movable-view.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-movable-view.jsx +6 -6
- package/lib/runtime/components/react/dist/mpx-picker-view/index.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-picker-view/index.jsx +5 -7
- package/lib/runtime/components/react/dist/mpx-picker-view-column/index.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-picker-view-column/index.jsx +2 -2
- package/lib/runtime/components/react/dist/mpx-progress.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-progress.jsx +1 -2
- package/lib/runtime/components/react/dist/mpx-radio-group.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-radio-group.jsx +3 -4
- package/lib/runtime/components/react/dist/mpx-radio.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-radio.jsx +8 -9
- package/lib/runtime/components/react/dist/mpx-rich-text/index.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-rich-text/index.jsx +1 -2
- package/lib/runtime/components/react/dist/mpx-scroll-view.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +6 -6
- package/lib/runtime/components/react/dist/mpx-simple-text.jsx +24 -19
- package/lib/runtime/components/react/dist/mpx-simple-view.d.ts +4 -1
- package/lib/runtime/components/react/dist/mpx-simple-view.jsx +11 -4
- package/lib/runtime/components/react/dist/mpx-slider.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-slider.jsx +6 -6
- package/lib/runtime/components/react/dist/mpx-sticky-header.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-sticky-header.jsx +5 -5
- package/lib/runtime/components/react/dist/mpx-sticky-section.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-sticky-section.jsx +6 -6
- package/lib/runtime/components/react/dist/mpx-swiper-item.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +5 -5
- package/lib/runtime/components/react/dist/mpx-swiper.d.ts +1 -1
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +4 -7
- package/lib/runtime/components/react/dist/mpx-switch.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-switch.jsx +1 -2
- package/lib/runtime/components/react/dist/mpx-text.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-text.jsx +29 -25
- package/lib/runtime/components/react/dist/mpx-video.d.ts +0 -1
- package/lib/runtime/components/react/dist/mpx-video.jsx +4 -4
- package/lib/runtime/components/react/dist/mpx-view.d.ts +2 -1
- package/lib/runtime/components/react/dist/mpx-view.jsx +214 -113
- package/lib/runtime/components/react/dist/mpx-web-view.d.ts +1 -2
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +5 -1
- package/lib/runtime/components/react/dist/namedColorSet.d.ts +2 -0
- package/lib/runtime/components/react/dist/namedColorSet.js +149 -0
- package/lib/runtime/components/react/dist/utils.d.ts +73 -70
- package/lib/runtime/components/react/dist/utils.jsx +1295 -445
- package/lib/runtime/components/react/event.config.ts +32 -25
- package/lib/runtime/components/react/getInnerListeners.ts +84 -60
- package/lib/runtime/components/react/mpx-button.tsx +18 -16
- package/lib/runtime/components/react/mpx-camera.tsx +0 -3
- package/lib/runtime/components/react/mpx-canvas/index.tsx +3 -7
- package/lib/runtime/components/react/mpx-canvas/utils.tsx +3 -3
- package/lib/runtime/components/react/mpx-checkbox-group.tsx +2 -6
- package/lib/runtime/components/react/mpx-checkbox.tsx +9 -11
- package/lib/runtime/components/react/mpx-form.tsx +6 -6
- package/lib/runtime/components/react/mpx-icon/index.tsx +1 -3
- package/lib/runtime/components/react/mpx-image.tsx +65 -54
- package/lib/runtime/components/react/mpx-input.tsx +1 -3
- package/lib/runtime/components/react/mpx-label.tsx +6 -8
- package/lib/runtime/components/react/mpx-movable-area.tsx +3 -4
- package/lib/runtime/components/react/mpx-movable-view.tsx +7 -7
- package/lib/runtime/components/react/mpx-picker-view/index.tsx +6 -8
- package/lib/runtime/components/react/mpx-picker-view-column/index.tsx +2 -4
- package/lib/runtime/components/react/mpx-progress.tsx +0 -3
- package/lib/runtime/components/react/mpx-radio-group.tsx +2 -6
- package/lib/runtime/components/react/mpx-radio.tsx +9 -11
- package/lib/runtime/components/react/mpx-rich-text/index.tsx +0 -3
- package/lib/runtime/components/react/mpx-scroll-view.tsx +7 -7
- package/lib/runtime/components/react/mpx-simple-text.tsx +24 -26
- package/lib/runtime/components/react/mpx-simple-view.tsx +17 -5
- package/lib/runtime/components/react/mpx-slider.tsx +15 -10
- package/lib/runtime/components/react/mpx-sticky-header.tsx +6 -6
- package/lib/runtime/components/react/mpx-sticky-section.tsx +7 -7
- package/lib/runtime/components/react/mpx-swiper-item.tsx +6 -6
- package/lib/runtime/components/react/mpx-swiper.tsx +5 -8
- package/lib/runtime/components/react/mpx-switch.tsx +0 -3
- package/lib/runtime/components/react/mpx-text.tsx +31 -34
- package/lib/runtime/components/react/mpx-video.tsx +3 -5
- package/lib/runtime/components/react/mpx-view.tsx +235 -126
- package/lib/runtime/components/react/mpx-web-view.tsx +11 -7
- package/lib/runtime/components/react/namedColorSet.ts +150 -0
- package/lib/runtime/components/react/types/getInnerListeners.d.ts +21 -1
- package/lib/runtime/components/react/types/global.d.ts +1 -0
- package/lib/runtime/components/react/utils.tsx +1348 -544
- package/lib/runtime/i18n.wxs +15 -9
- package/lib/style-compiler/strip-conditional.js +19 -4
- package/package.json +2 -2
- package/lib/resolver/ExtendComponentsPlugin.js +0 -91
- package/lib/runtime/components/extends/section-list.mpx +0 -14
- package/lib/runtime/components/react/dist/mpx-section-list.d.ts +0 -56
- package/lib/runtime/components/react/dist/mpx-section-list.jsx +0 -484
- package/lib/runtime/components/react/mpx-section-list.tsx +0 -734
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const { hump2dash } = require('../../../utils/hump-dash')
|
|
2
2
|
const { parseValues } = require('../../../utils/string')
|
|
3
3
|
|
|
4
|
-
module.exports = function getSpec({ warn, error }) {
|
|
4
|
+
module.exports = function getSpec ({ warn, error }) {
|
|
5
5
|
// React Native 双端都不支持的 CSS property
|
|
6
|
-
|
|
6
|
+
// border-*-style 在 RN 双端都不支持,仅支持统一的 border-style;shorthand 路径会展开到 border-style,长属性写法直接拦截
|
|
7
|
+
const unsupportedPropExp = /^(white-space|text-overflow|animation|font-variant-caps|font-variant-numeric|font-variant-east-asian|font-variant-alternates|font-variant-ligatures|caret-color|float|clear|border-(top|right|bottom|left)-style)$/
|
|
7
8
|
const unsupportedPropMode = {
|
|
8
9
|
// React Native ios 不支持的 CSS property
|
|
9
10
|
ios: /^(vertical-align)$/,
|
|
@@ -12,12 +13,22 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
12
13
|
// TODO: rnoh 文档暂未找到 css 属性支持说明,暂时同步 android,同时需要注意此处校验是否有缺失,类似 will-change 之类属性
|
|
13
14
|
harmony: /^(text-decoration-style|text-decoration-color|shadow-offset|shadow-opacity|shadow-radius)$/
|
|
14
15
|
}
|
|
16
|
+
const isNum = (v) => !isNaN(+v)
|
|
15
17
|
// var(xx)
|
|
16
18
|
const cssVariableExp = /var\(/
|
|
17
19
|
// calc(xx)
|
|
18
20
|
const calcExp = /calc\(/
|
|
19
21
|
const envExp = /env\(/
|
|
20
22
|
const silentVerify = 'silent'
|
|
23
|
+
const namedColorSet = new Set(['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'])
|
|
24
|
+
const hexColorExp = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
|
25
|
+
const colorFnExp = /^(rgb|rgba|hsl|hsla|hwb)\(.+\)$/
|
|
26
|
+
const valueExp = {
|
|
27
|
+
integer: { test: isNum },
|
|
28
|
+
length: /^((-?(\d+(\.\d+)?|\.\d+))(rpx|px|%|vw|vh)?|hairlineWidth)$/,
|
|
29
|
+
color: { test: (v) => namedColorSet.has(v) || hexColorExp.test(v) || colorFnExp.test(v) }
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
// 不支持的属性提示
|
|
22
33
|
const unsupportedPropError = ({ prop, value, selector }, { mode }, isError = true) => {
|
|
23
34
|
const tips = isError ? error : warn
|
|
@@ -39,12 +50,18 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
39
50
|
color: 'color',
|
|
40
51
|
enum: 'enum'
|
|
41
52
|
}
|
|
53
|
+
// 校验失败提示中的示例值(按类型)。enum 依赖具体 prop 的白名单,在 tipsType 内动态拼。
|
|
54
|
+
const valueTypeExample = {
|
|
55
|
+
[ValueType.length]: '2rpx,10%,30rpx',
|
|
56
|
+
[ValueType.color]: 'rgb,rgba,hsl,hsla,hwb,named color,#000000'
|
|
57
|
+
}
|
|
42
58
|
// React 属性支持的枚举值
|
|
43
59
|
const SUPPORTED_PROP_VAL_ARR = {
|
|
44
60
|
'box-sizing': ['border-box', 'content-box'],
|
|
45
61
|
'backface-visibility': ['visible', 'hidden'],
|
|
46
62
|
overflow: ['visible', 'hidden', 'scroll'],
|
|
47
|
-
|
|
63
|
+
// RN 实测仅支持 solid/dotted/dashed;none 作为 CSS 合法值保留到运行时统一转换为 borderWidth: 0
|
|
64
|
+
'border-style': ['solid', 'dotted', 'dashed', 'none'],
|
|
48
65
|
'object-fit': ['cover', 'contain', 'fill', 'scale-down'],
|
|
49
66
|
direction: ['inherit', 'ltr', 'rtl'],
|
|
50
67
|
display: ['flex', 'none'],
|
|
@@ -77,7 +94,9 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
77
94
|
'margin-bottom': ['auto', ValueType.length],
|
|
78
95
|
'margin-right': ['auto', ValueType.length],
|
|
79
96
|
'margin-horizontal': ['auto', ValueType.length],
|
|
80
|
-
'margin-vertical': ['auto', ValueType.length]
|
|
97
|
+
'margin-vertical': ['auto', ValueType.length],
|
|
98
|
+
// outline-style 走 enum 校验;与 border-style 对齐,不支持 double / groove / ridge
|
|
99
|
+
'outline-style': ['solid', 'dotted', 'dashed', 'none']
|
|
81
100
|
}
|
|
82
101
|
// 获取值类型
|
|
83
102
|
const getValueType = (prop) => {
|
|
@@ -119,7 +138,7 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
119
138
|
const verifyValues = ({ prop, value, selector }, isError = true) => {
|
|
120
139
|
prop = prop.trim()
|
|
121
140
|
const rawValue = value.trim()
|
|
122
|
-
const tips = isError === silentVerify ? () => {} : isError ? error : warn
|
|
141
|
+
const tips = isError === silentVerify ? () => { } : isError ? error : warn
|
|
123
142
|
|
|
124
143
|
// CSS 自定义属性(--xxx)是变量定义,不属于 RN 样式属性:
|
|
125
144
|
// 不能按 `-height/-color` 等后缀推断类型去校验,否则会把变量定义错误过滤,导致运行时 var() 取值失败
|
|
@@ -145,20 +164,12 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
145
164
|
|
|
146
165
|
// calc() / env() 跳过值校验,但保留 rawValue 输出
|
|
147
166
|
if (calcExp.test(valueForVerify) || envExp.test(valueForVerify)) return true
|
|
148
|
-
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']
|
|
149
|
-
const valueExp = {
|
|
150
|
-
integer: /^(-?(\d+(\.\d+)?|\.\d+))$/,
|
|
151
|
-
length: /^((-?(\d+(\.\d+)?|\.\d+))(rpx|px|%|vw|vh)?|hairlineWidth)$/,
|
|
152
|
-
color: new RegExp(('^(' + namedColor.join('|') + ')$') + '|(^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$)|^(rgb|rgba|hsl|hsla|hwb)\\(.+\\)$')
|
|
153
|
-
}
|
|
154
167
|
const type = getValueType(prop)
|
|
155
168
|
const tipsType = (type) => {
|
|
156
|
-
const
|
|
157
|
-
[
|
|
158
|
-
[
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
tips(`Value of ${prop} in ${selector} should be ${type}${info[type] ? `, eg ${info[type]}` : ''}, received [${rawValue}], please check again!`)
|
|
169
|
+
const example = type === ValueType.enum
|
|
170
|
+
? SUPPORTED_PROP_VAL_ARR[prop]?.join(',')
|
|
171
|
+
: valueTypeExample[type]
|
|
172
|
+
tips(`Value of ${prop} in ${selector} should be ${type}${example ? `, eg ${example}` : ''}, received [${rawValue}], please check again!`)
|
|
162
173
|
}
|
|
163
174
|
switch (type) {
|
|
164
175
|
case ValueType.length: {
|
|
@@ -183,6 +194,17 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
183
194
|
return true
|
|
184
195
|
}
|
|
185
196
|
case ValueType.enum: {
|
|
197
|
+
// font-variant 长属性允许空格分隔的多值(如 `small-caps tabular-nums`),
|
|
198
|
+
// 走整串比对会误杀多值场景;拆 token 后逐个命中枚举白名单即放行。
|
|
199
|
+
// 输出仍保留 raw value,运行时由 RN `processFontVariant` 自行 `split(' ')` 归一为数组。
|
|
200
|
+
if (prop === 'font-variant') {
|
|
201
|
+
const tokens = parseValues(valueForVerify)
|
|
202
|
+
if (!tokens.length || !tokens.every(t => SUPPORTED_PROP_VAL_ARR[prop].includes(t))) {
|
|
203
|
+
tipsType(type)
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
return true
|
|
207
|
+
}
|
|
186
208
|
const isIn = SUPPORTED_PROP_VAL_ARR[prop].includes(valueForVerify)
|
|
187
209
|
const isType = Object.keys(valueExp).some(item => valueExp[item].test(valueForVerify) && SUPPORTED_PROP_VAL_ARR[prop].includes(ValueType[item]))
|
|
188
210
|
if (!isIn && !isType) {
|
|
@@ -201,22 +223,15 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
201
223
|
|
|
202
224
|
// 简写转换规则
|
|
203
225
|
const AbbreviationMap = {
|
|
204
|
-
// 仅支持 offset-x | offset-y | blur-radius | color 排序
|
|
205
226
|
'text-shadow': ['textShadowOffset.width', 'textShadowOffset.height', 'textShadowRadius', 'textShadowColor'],
|
|
206
|
-
// 仅支持 width | style | color 这种排序
|
|
207
227
|
border: ['borderWidth', 'borderStyle', 'borderColor'],
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
//
|
|
211
|
-
'border-
|
|
212
|
-
|
|
213
|
-
'border-top': ['borderTopWidth', '
|
|
214
|
-
|
|
215
|
-
'border-bottom': ['borderBottomWidth', 'borderBottomStyle', 'borderBottomColor'],
|
|
216
|
-
// 0.76 及以上版本RN支持 box-shadow,实测0.77版本drn红米note12pro Android12 不支持内阴影,其他表现和web一致
|
|
217
|
-
// 仅支持 offset-x | offset-y | blur-radius | color 排序
|
|
218
|
-
// 'box-shadow': ['shadowOffset.width', 'shadowOffset.height', 'shadowRadius', 'shadowColor'],
|
|
219
|
-
// 仅支持 text-decoration-line text-decoration-style text-decoration-color 这种格式
|
|
228
|
+
// RN 不支持单边 border-*-style,统一展开到 borderStyle
|
|
229
|
+
// 实测 RN 上当 borderStyle 不为 solid 时单边 border-*-color 不生效,
|
|
230
|
+
// 这里把单边 color 也统一展开到 borderColor 规避(width 不能这样做,否则会覆盖其它三边)
|
|
231
|
+
'border-left': ['borderLeftWidth', 'borderStyle', 'borderColor'],
|
|
232
|
+
'border-right': ['borderRightWidth', 'borderStyle', 'borderColor'],
|
|
233
|
+
'border-top': ['borderTopWidth', 'borderStyle', 'borderColor'],
|
|
234
|
+
'border-bottom': ['borderBottomWidth', 'borderStyle', 'borderColor'],
|
|
220
235
|
'text-decoration': ['textDecorationLine', 'textDecorationStyle', 'textDecorationColor'],
|
|
221
236
|
// flex-grow | flex-shrink | flex-basis
|
|
222
237
|
flex: ['flexGrow', 'flexShrink', 'flexBasis'],
|
|
@@ -226,17 +241,166 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
226
241
|
'border-width': ['borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'],
|
|
227
242
|
'border-color': ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'],
|
|
228
243
|
margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
|
|
229
|
-
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft']
|
|
244
|
+
padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
|
|
245
|
+
// gap:CSS 2 槽位(row-gap / column-gap),单值复制行列
|
|
246
|
+
gap: ['rowGap', 'columnGap'],
|
|
247
|
+
// inset:4 槽位等价 margin 四值语法;RN inset 长属性不稳定,单值也强制展开
|
|
248
|
+
inset: ['top', 'right', 'bottom', 'left'],
|
|
249
|
+
// outline:RN 0.74 不支持、0.76+ 生效;走无序展开,与 border 共享缺省补齐
|
|
250
|
+
outline: ['outlineWidth', 'outlineStyle', 'outlineColor']
|
|
230
251
|
}
|
|
252
|
+
|
|
253
|
+
// 这些简写按 CSS 规范允许 token 顺序自由排列,按值类型识别归位
|
|
254
|
+
const UnorderedAbbreviationMap = {
|
|
255
|
+
'text-shadow': true,
|
|
256
|
+
'text-decoration': true,
|
|
257
|
+
'flex-flow': true,
|
|
258
|
+
border: true,
|
|
259
|
+
'border-left': true,
|
|
260
|
+
'border-right': true,
|
|
261
|
+
'border-top': true,
|
|
262
|
+
'border-bottom': true,
|
|
263
|
+
// outline: <outline-width> || <outline-style> || <outline-color>,顺序不敏感
|
|
264
|
+
outline: true
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// CSS border-width: medium 的实测值(各主流浏览器一致取 3px)
|
|
268
|
+
// CSS 规范允许 medium 实现自定,这里取业界事实标准;调整此值需与运行时同名常量一起改
|
|
269
|
+
const BORDER_MEDIUM_WIDTH = 3
|
|
270
|
+
// 简写槽位缺省值表(数据驱动;新增简写或调整缺省值只改这里)
|
|
271
|
+
// 值即槽位缺省时追加的补齐值;none 清除语义统一保留到运行时处理
|
|
272
|
+
// 注意:borderColor / textShadowRadius 因 RN 有内置缺省值,无需补齐,不进此表
|
|
273
|
+
const ShorthandDefaultMap = {
|
|
274
|
+
border: { borderWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
275
|
+
'border-top': { borderTopWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
276
|
+
'border-right': { borderRightWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
277
|
+
'border-bottom': { borderBottomWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
278
|
+
'border-left': { borderLeftWidth: BORDER_MEDIUM_WIDTH, borderStyle: 'none' },
|
|
279
|
+
// outline 与 border 缺省值完全对齐:缺 width → BORDER_MEDIUM_WIDTH;
|
|
280
|
+
// 缺 style → outlineStyle: none,运行时统一转换为 outlineWidth: 0
|
|
281
|
+
outline: { outlineWidth: BORDER_MEDIUM_WIDTH, outlineStyle: 'none' },
|
|
282
|
+
'text-shadow': {
|
|
283
|
+
// textShadowOffset.height 的「width 存在才补 0」由 formatUnorderedAbbreviation 内既有 fallback 处理
|
|
284
|
+
// 值不带 quote,由后续 style-helper 的 formatValue 统一 JSON.stringify
|
|
285
|
+
textShadowColor: '#000'
|
|
286
|
+
}
|
|
287
|
+
// text-decoration / flex-flow 暂不配置,与 RN 默认一致
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 通用补齐:扫描完所有 token 后,将 ShorthandDefaultMap 中未被占用(不在 used)的槽位追加到 cssMap
|
|
291
|
+
// used 即主循环的占用记录,key 是完整目标 prop 名(含 textShadowOffset.width 这类 dot 路径)
|
|
292
|
+
const applyShorthandDefaults = (cssMap, prop, used) => {
|
|
293
|
+
const defaults = ShorthandDefaultMap[prop]
|
|
294
|
+
if (!defaults) return cssMap
|
|
295
|
+
for (const target in defaults) {
|
|
296
|
+
if (!used[target]) {
|
|
297
|
+
pushAbbreviationValue(cssMap, target, defaults[target])
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return cssMap
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const pushAbbreviationValue = (cssMap, prop, value) => {
|
|
304
|
+
if (prop.includes('.')) {
|
|
305
|
+
// 多个属性值的prop
|
|
306
|
+
const [main, sub] = prop.split('.')
|
|
307
|
+
const cssData = cssMap.find(item => item.prop === main)
|
|
308
|
+
if (cssData) { // 设置过
|
|
309
|
+
cssData.value[sub] = value
|
|
310
|
+
} else { // 第一次设置
|
|
311
|
+
cssMap.push({
|
|
312
|
+
prop: main,
|
|
313
|
+
value: {
|
|
314
|
+
[sub]: value
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
// 单个值的属性
|
|
320
|
+
cssMap.push({
|
|
321
|
+
prop,
|
|
322
|
+
value
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const getVerifiedProp = (props, value, selector, mode, used) => {
|
|
328
|
+
return props.find(prop => {
|
|
329
|
+
if (used[prop]) return false
|
|
330
|
+
const newProp = hump2dash(prop.replace(/\..+/, ''))
|
|
331
|
+
return verifyValues({ prop: newProp, value, selector }, silentVerify) &&
|
|
332
|
+
verifyProps({ prop: newProp, value, selector }, { mode }, false)
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const formatUnorderedAbbreviation = ({ prop, value, selector }, { mode }) => {
|
|
337
|
+
const originalValue = value
|
|
338
|
+
const values = Array.isArray(value) ? value : parseValues(value)
|
|
339
|
+
const original = `${prop}:${originalValue}`
|
|
340
|
+
const props = AbbreviationMap[prop]
|
|
341
|
+
const cssMap = []
|
|
342
|
+
const used = {}
|
|
343
|
+
let hasTextDecorationNone = false
|
|
344
|
+
let hasUnderline = false
|
|
345
|
+
let hasLineThrough = false
|
|
346
|
+
// values[0] 而非 originalValue:避免 originalValue 是数组时 toString() 误命中
|
|
347
|
+
if (values.length === 1 && cssVariableExp.test(values[0])) {
|
|
348
|
+
return { prop, value: values[0] }
|
|
349
|
+
}
|
|
350
|
+
values.forEach(value => {
|
|
351
|
+
if (prop === 'text-decoration' && verifyValues({ prop: 'text-decoration-line', value, selector }, silentVerify)) {
|
|
352
|
+
switch (value) {
|
|
353
|
+
case 'underline':
|
|
354
|
+
hasUnderline = true
|
|
355
|
+
return
|
|
356
|
+
case 'line-through':
|
|
357
|
+
hasLineThrough = true
|
|
358
|
+
return
|
|
359
|
+
case 'none':
|
|
360
|
+
hasTextDecorationNone = true
|
|
361
|
+
return
|
|
362
|
+
// verifyValues 通过但分支未处理的 line token:落到通用 getVerifiedProp 流程
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const matchedProp = getVerifiedProp(props, value, selector, mode, used)
|
|
366
|
+
if (!matchedProp) {
|
|
367
|
+
warn(`Value of [${original}] in ${selector} is invalid, received [${value}], please check again!`)
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
used[matchedProp] = true
|
|
371
|
+
pushAbbreviationValue(cssMap, matchedProp, value)
|
|
372
|
+
})
|
|
373
|
+
if (prop === 'text-decoration') {
|
|
374
|
+
if (hasUnderline || hasLineThrough) {
|
|
375
|
+
pushAbbreviationValue(cssMap, 'textDecorationLine', hasUnderline && hasLineThrough ? 'underline line-through' : hasUnderline ? 'underline' : 'line-through')
|
|
376
|
+
} else if (hasTextDecorationNone) {
|
|
377
|
+
pushAbbreviationValue(cssMap, 'textDecorationLine', 'none')
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (prop === 'text-shadow') {
|
|
381
|
+
// text-shadow 至少需要 offset-x 与 offset-y;缺省 height 时按 CSS 默认补 0,避免 RN 上 textShadowOffset.height 缺失
|
|
382
|
+
const shadowOffsetEntry = cssMap.find(item => item.prop === 'textShadowOffset')
|
|
383
|
+
if (shadowOffsetEntry) {
|
|
384
|
+
const offsetVal = shadowOffsetEntry.value
|
|
385
|
+
if (offsetVal && offsetVal.width !== undefined && offsetVal.height === undefined) {
|
|
386
|
+
warn(`Value of [${original}] in ${selector} is missing offset-y, fallback to 0, please check again!`)
|
|
387
|
+
offsetVal.height = 0
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return applyShorthandDefaults(cssMap, prop, used)
|
|
392
|
+
}
|
|
393
|
+
|
|
231
394
|
const formatAbbreviation = ({ prop, value, selector }, { mode }) => {
|
|
395
|
+
if (UnorderedAbbreviationMap[prop]) {
|
|
396
|
+
return formatUnorderedAbbreviation({ prop, value, selector }, { mode })
|
|
397
|
+
}
|
|
232
398
|
const original = `${prop}:${value}`
|
|
233
399
|
const props = AbbreviationMap[prop]
|
|
234
400
|
const values = Array.isArray(value) ? value : parseValues(value)
|
|
235
401
|
const cssMap = []
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
error(`Property ${prop} in ${selector} is abbreviated property and does not support a single CSS var`)
|
|
239
|
-
return cssMap
|
|
402
|
+
if (values.length === 1 && cssVariableExp.test(values[0])) {
|
|
403
|
+
return { prop, value: values[0] }
|
|
240
404
|
}
|
|
241
405
|
let idx = 0
|
|
242
406
|
let propsIdx = 0
|
|
@@ -267,28 +431,8 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
267
431
|
} else {
|
|
268
432
|
idx++
|
|
269
433
|
}
|
|
270
|
-
} else if (prop.includes('.')) {
|
|
271
|
-
// 多个属性值的prop
|
|
272
|
-
const [main, sub] = prop.split('.')
|
|
273
|
-
const cssData = cssMap.find(item => item.prop === main)
|
|
274
|
-
if (cssData) { // 设置过
|
|
275
|
-
cssData.value[sub] = value
|
|
276
|
-
} else { // 第一次设置
|
|
277
|
-
cssMap.push({
|
|
278
|
-
prop: main,
|
|
279
|
-
value: {
|
|
280
|
-
[sub]: value
|
|
281
|
-
}
|
|
282
|
-
})
|
|
283
|
-
}
|
|
284
|
-
idx += 1
|
|
285
|
-
propsIdx += 1
|
|
286
434
|
} else {
|
|
287
|
-
|
|
288
|
-
cssMap.push({
|
|
289
|
-
prop,
|
|
290
|
-
value
|
|
291
|
-
})
|
|
435
|
+
pushAbbreviationValue(cssMap, prop, value)
|
|
292
436
|
idx += 1
|
|
293
437
|
propsIdx += 1
|
|
294
438
|
}
|
|
@@ -296,18 +440,31 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
296
440
|
return cssMap
|
|
297
441
|
}
|
|
298
442
|
|
|
443
|
+
// 单值直接返回(RN 原生支持单值 DimensionValue)的属性:margin / padding / border-* 四值简写
|
|
444
|
+
// 不含 gap / inset:
|
|
445
|
+
// - inset:RN inset 长属性非稳定,单值透传会留下不可靠的 inset key,必须展开到 top/right/bottom/left
|
|
446
|
+
// - gap:RN gap / rowGap / columnGap 只接受 number;单值串虽然后续 style-helper formatValue 也会处理,
|
|
447
|
+
// 但展开到 rowGap / columnGap 与运行时 runtimeForceExpandCompositeMap 行为保持对齐,更可读
|
|
448
|
+
const compositeSingleValuePassthrough = (prop) => prop !== 'gap' && prop !== 'inset'
|
|
449
|
+
|
|
299
450
|
const formatCompositeVal = ({ prop, value, selector }, { mode }) => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
451
|
+
// 槽位数由 AbbreviationMap[prop].length 决定(gap=2,inset/margin/padding/border-*=4)
|
|
452
|
+
const count = AbbreviationMap[prop].length
|
|
453
|
+
const values = parseValues(value).splice(0, count)
|
|
454
|
+
// 单值短路:margin / padding 等 RN 原生支持单值,原样透传;gap / inset 单值也需展开,不走此捷径
|
|
455
|
+
if (values.length === 1 && compositeSingleValuePassthrough(prop)) {
|
|
456
|
+
verifyValues({ prop, value, selector }, false)
|
|
457
|
+
return { prop, value }
|
|
458
|
+
}
|
|
459
|
+
if (count === 2) {
|
|
460
|
+
// gap:单值复制到行列两槽;双值原样
|
|
461
|
+
if (values.length === 1) values.push(values[0])
|
|
462
|
+
} else {
|
|
463
|
+
switch (values.length) {
|
|
464
|
+
case 1: values.push(values[0], values[0], values[0]); break // 仅 inset 命中(margin/padding 已被上面短路)
|
|
465
|
+
case 2: values.push(...values); break
|
|
466
|
+
case 3: values.push(values[1]); break
|
|
467
|
+
}
|
|
311
468
|
}
|
|
312
469
|
return formatAbbreviation({ prop, value: values, selector }, { mode })
|
|
313
470
|
}
|
|
@@ -318,19 +475,19 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
318
475
|
if (+value === 0) {
|
|
319
476
|
return {
|
|
320
477
|
prop,
|
|
321
|
-
value
|
|
478
|
+
value: 0
|
|
322
479
|
}
|
|
323
480
|
}
|
|
324
481
|
return verifyValues({ prop, value, selector }) && ({
|
|
325
482
|
prop,
|
|
326
|
-
value:
|
|
483
|
+
value: isNum(value) ? `${Math.round(value * 100)}%` : value
|
|
327
484
|
})
|
|
328
485
|
}
|
|
329
486
|
|
|
330
487
|
// background 相关属性的转换 Todo
|
|
331
488
|
// 仅支持以下属性,不支持其他背景相关的属性
|
|
332
489
|
// /^((?!(-color)).)*background((?!(-color)).)*$/ 包含background且不包含background-color
|
|
333
|
-
const
|
|
490
|
+
const formatBackground = ({ prop, value, selector }, { mode }) => {
|
|
334
491
|
const bgPropMap = {
|
|
335
492
|
image: 'background-image',
|
|
336
493
|
color: 'background-color',
|
|
@@ -360,15 +517,21 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
360
517
|
return values.length === 0 ? false : { prop: bgPropMap.size, value: values }
|
|
361
518
|
}
|
|
362
519
|
const formatBackgroundPosition = (value) => {
|
|
520
|
+
const yAxisKeywords = ['top', 'bottom']
|
|
363
521
|
const values = []
|
|
364
522
|
parseValues(value).forEach(item => {
|
|
365
523
|
if (verifyValues({ prop: bgPropMap.position, value: item, selector })) {
|
|
366
524
|
// 支持 number 值 / 枚举, center与50%等价
|
|
367
525
|
values.push(item === 'center' ? '50%' : item)
|
|
368
526
|
} else {
|
|
369
|
-
|
|
527
|
+
// 仅丢这一 token,其它 push 的位置仍输出,按规范使用 warn
|
|
528
|
+
warn(`Value of [${bgPropMap.position}] in ${selector} does not support value [${item}], please check again!`)
|
|
370
529
|
}
|
|
371
530
|
})
|
|
531
|
+
// CSS 允许 y x 顺序的关键字(如 top left),但输出需要 [x, y] 顺序
|
|
532
|
+
if (values.length === 2 && yAxisKeywords.includes(values[0])) {
|
|
533
|
+
;[values[0], values[1]] = [values[1], values[0]]
|
|
534
|
+
}
|
|
372
535
|
return { prop: bgPropMap.position, value: values }
|
|
373
536
|
}
|
|
374
537
|
switch (prop) {
|
|
@@ -391,8 +554,7 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
391
554
|
case bgPropMap.all: {
|
|
392
555
|
// background: 支持 image/color/repeat 与 position/size
|
|
393
556
|
if (cssVariableExp.test(value)) {
|
|
394
|
-
|
|
395
|
-
return false
|
|
557
|
+
return { prop, value }
|
|
396
558
|
}
|
|
397
559
|
// background: none
|
|
398
560
|
if (value === 'none') {
|
|
@@ -409,9 +571,13 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
409
571
|
if (isSize) {
|
|
410
572
|
if (verifyValues({ prop: bgPropMap.size, value: item, selector }, silentVerify)) {
|
|
411
573
|
sizeValues.push(item)
|
|
574
|
+
} else {
|
|
575
|
+
warn(`Value of [${bgPropMap.all}:${value}] in ${selector} does not support background-size token [${item}], please check again!`)
|
|
412
576
|
}
|
|
413
577
|
} else if (verifyValues({ prop: bgPropMap.position, value: item, selector }, silentVerify)) {
|
|
414
578
|
positionValues.push(item)
|
|
579
|
+
} else {
|
|
580
|
+
warn(`Value of [${bgPropMap.all}:${value}] in ${selector} does not support background-position token [${item}], please check again!`)
|
|
415
581
|
}
|
|
416
582
|
}
|
|
417
583
|
const handlePositionSize = (item) => {
|
|
@@ -445,8 +611,10 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
445
611
|
bgMap.push({ prop: bgPropMap.color, value: item })
|
|
446
612
|
} else if (verifyValues({ prop: bgPropMap.repeat, value: item, selector }, silentVerify)) {
|
|
447
613
|
bgMap.push({ prop: bgPropMap.repeat, value: item })
|
|
448
|
-
} else {
|
|
449
|
-
|
|
614
|
+
} else if (!handlePositionSize(item)) {
|
|
615
|
+
// 既不是 url / linear-gradient / color / repeat / position-size,也不是合法 position 起头的 token
|
|
616
|
+
// 典型场景:background-attachment / background-origin / background-clip 及拼写错误,RN 均不支持
|
|
617
|
+
warn(`Value of [${bgPropMap.all}:${value}] in ${selector} does not support token [${item}], please check again!`)
|
|
450
618
|
}
|
|
451
619
|
})
|
|
452
620
|
if (positionValues.length) {
|
|
@@ -495,9 +663,6 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
495
663
|
key = key === 'rotate' ? 'rotateZ' : key
|
|
496
664
|
transform.push({ [key]: val })
|
|
497
665
|
break
|
|
498
|
-
case 'matrix':
|
|
499
|
-
transform.push({ [key]: parseValues(val, ',').map(val => +val) })
|
|
500
|
-
break
|
|
501
666
|
case 'translate':
|
|
502
667
|
case 'scale':
|
|
503
668
|
case 'skew':
|
|
@@ -512,25 +677,69 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
512
677
|
vals.push(vals[0])
|
|
513
678
|
}
|
|
514
679
|
const xyz = ['X', 'Y', 'Z']
|
|
515
|
-
|
|
680
|
+
vals.forEach((v, index) => {
|
|
516
681
|
if (key !== 'rotate' && index > 1) {
|
|
517
|
-
|
|
682
|
+
// 局部丢弃:仅这一维 (Z) 对应的子项被推出去也是错的,但 X/Y 仍正常输出,按规范使用 warn
|
|
683
|
+
unsupportedPropError({ prop: `${key}Z`, value, selector }, { mode }, false)
|
|
684
|
+
return
|
|
518
685
|
}
|
|
519
|
-
|
|
520
|
-
|
|
686
|
+
// parseValues 内部已 trim,这里不再重复
|
|
687
|
+
transform.push({ [`${key}${xyz[index] || ''}`]: v })
|
|
688
|
+
})
|
|
689
|
+
break
|
|
690
|
+
}
|
|
691
|
+
case 'rotate3d': {
|
|
692
|
+
const parts = parseValues(val, ',')
|
|
693
|
+
if (parts.length === 4) {
|
|
694
|
+
// parseValues 内部已 trim
|
|
695
|
+
const x = +parts[0]
|
|
696
|
+
const y = +parts[1]
|
|
697
|
+
const z = +parts[2]
|
|
698
|
+
const angle = parts[3]
|
|
699
|
+
if (x && !y && !z) transform.push({ rotateX: angle })
|
|
700
|
+
else if (!x && y && !z) transform.push({ rotateY: angle })
|
|
701
|
+
else if (!x && !y && z) transform.push({ rotateZ: angle })
|
|
702
|
+
// 仅丢这一 transform 项,其它项仍输出,按规范使用 warn
|
|
703
|
+
else unsupportedPropError({ prop, value, selector }, { mode }, false)
|
|
704
|
+
} else {
|
|
705
|
+
warn(`Value of [transform] in ${selector} does not support rotate3d with ${parts.length} values, only 4 values are supported, please check again!`)
|
|
706
|
+
}
|
|
707
|
+
break
|
|
708
|
+
}
|
|
709
|
+
case 'matrix':
|
|
710
|
+
{
|
|
711
|
+
const matrixValues = parseValues(val, ',').map(val => +val)
|
|
712
|
+
if (matrixValues.length === 6) {
|
|
713
|
+
const [a, b, c, d, tx, ty] = matrixValues
|
|
714
|
+
transform.push({ matrix: [a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1] })
|
|
715
|
+
} else {
|
|
716
|
+
// 仅丢这一 transform 项,其它项仍输出,按规范使用 warn
|
|
717
|
+
warn(`Value of [transform] in ${selector} does not support matrix with ${matrixValues.length} values, only 6 values are supported in ${mode} environment, please check again!`)
|
|
718
|
+
}
|
|
521
719
|
break
|
|
522
720
|
}
|
|
721
|
+
case 'matrix3d':
|
|
722
|
+
{
|
|
723
|
+
const matrixValues = parseValues(val, ',').map(val => +val)
|
|
724
|
+
if (matrixValues.length === 16) {
|
|
725
|
+
transform.push({ matrix: matrixValues })
|
|
726
|
+
} else {
|
|
727
|
+
// 仅丢这一 transform 项,其它项仍输出,按规范使用 warn
|
|
728
|
+
warn(`Value of [transform] in ${selector} does not support matrix3d with ${matrixValues.length} values, only 16 values are supported in ${mode} environment, please check again!`)
|
|
729
|
+
}
|
|
730
|
+
break
|
|
731
|
+
}
|
|
732
|
+
// 不支持的属性处理
|
|
523
733
|
case 'translateZ':
|
|
524
734
|
case 'scaleZ':
|
|
525
|
-
case 'rotate3d': // x y z angle
|
|
526
|
-
case 'matrix3d':
|
|
527
735
|
default:
|
|
528
|
-
//
|
|
529
|
-
unsupportedPropError({ prop, value, selector }, { mode })
|
|
736
|
+
// 仅丢这一 transform 项,其它项仍输出,按规范使用 warn
|
|
737
|
+
unsupportedPropError({ prop, value, selector }, { mode }, false)
|
|
530
738
|
break
|
|
531
739
|
}
|
|
532
740
|
} else {
|
|
533
|
-
|
|
741
|
+
// 仅丢这一 transform 项,其它项仍输出,按规范使用 warn
|
|
742
|
+
warn(`Property [${prop}] is invalid in ${selector}, received [${value}], please check again!`)
|
|
534
743
|
}
|
|
535
744
|
})
|
|
536
745
|
return {
|
|
@@ -547,7 +756,8 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
547
756
|
if ((isNumber(value) && value >= 0) || cssVariableExp.test(value)) {
|
|
548
757
|
return { prop, value }
|
|
549
758
|
} else {
|
|
550
|
-
|
|
759
|
+
// 仅这一槽位被丢,调用侧 cssMap 仍会输出其它槽位,按规范使用 warn
|
|
760
|
+
warn(`Value of [${prop}] in ${selector} accepts any floating point value >= 0, received [${value}], please check again!`)
|
|
551
761
|
return false
|
|
552
762
|
}
|
|
553
763
|
}
|
|
@@ -616,7 +826,8 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
616
826
|
} else if (values.length > 1) {
|
|
617
827
|
warn(`Value of [${prop}] only supports one in ${selector}, received [${value}], and the first one is used by default.`)
|
|
618
828
|
}
|
|
619
|
-
|
|
829
|
+
// parseValues 内部已 trim,这里无需再 trim
|
|
830
|
+
return { prop, value: values[0] }
|
|
620
831
|
}
|
|
621
832
|
|
|
622
833
|
// const formatBoxShadow = ({ prop, value, selector }, { mode }) => {
|
|
@@ -633,16 +844,90 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
633
844
|
// })
|
|
634
845
|
// return cssMap
|
|
635
846
|
// }
|
|
636
|
-
|
|
847
|
+
|
|
848
|
+
// font 简写专用 formatter。RN 等效子集语法:
|
|
849
|
+
// font: [ <font-style> ] [ <font-variant-css2> ] [ <font-weight> ] <font-size> [ / <line-height> ] <font-family>
|
|
850
|
+
// - 必填项:font-size 与 font-family;缺其一整条声明丢弃(error)
|
|
851
|
+
// - 非必填 token(font-stretch / 数字型 font-variant-numeric / system 关键字等):warn 提示并忽略,保留其余槽位
|
|
852
|
+
// - font-variant 仅支持 small-caps(CSS font 简写规范 <font-variant-css2>),与 RN processFontVariant 同口径(字符串透传,内部 split 为数组)
|
|
853
|
+
const formatFont = ({ prop, value, selector }, { mode }) => {
|
|
637
854
|
value = value.trim()
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
855
|
+
// 单 var() 兜底:编译期无法判断内部 token,原样返回交给运行时解析
|
|
856
|
+
if (cssVariableExp.test(value) && parseValues(value).length === 1) {
|
|
857
|
+
return { prop, value }
|
|
858
|
+
}
|
|
859
|
+
const tokens = parseValues(value)
|
|
860
|
+
const cssMap = []
|
|
861
|
+
let sizeIdx = -1
|
|
862
|
+
let lineHeight
|
|
863
|
+
// 1. 定位 font-size(第一个 length 类型 token,可能带 /<line-height>)
|
|
864
|
+
// 注意:unit-less 数字也命中 length 正则,需要先排除 font-weight 数字(100..900 / bold / normal),
|
|
865
|
+
// 否则 `font: small-caps 500 28rpx Arial` 会把 500 误判为 fontSize。
|
|
866
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
867
|
+
let t = tokens[i]
|
|
868
|
+
if (t.endsWith('/') && tokens[i + 1]) {
|
|
869
|
+
t += tokens[i + 1]
|
|
870
|
+
tokens.splice(i + 1, 1)
|
|
871
|
+
} else if (tokens[i + 1] === '/' && tokens[i + 2]) {
|
|
872
|
+
t += `/${tokens[i + 2]}`
|
|
873
|
+
tokens.splice(i + 1, 2)
|
|
874
|
+
} else if (tokens[i + 1]?.startsWith('/') && tokens[i + 1].length > 1) {
|
|
875
|
+
t += tokens[i + 1]
|
|
876
|
+
tokens.splice(i + 1, 1)
|
|
642
877
|
}
|
|
643
|
-
|
|
644
|
-
|
|
878
|
+
const [sizePart, lhPart] = parseValues(t, '/')
|
|
879
|
+
if (verifyValues({ prop: 'font-weight', value: sizePart, selector }, silentVerify)) continue
|
|
880
|
+
if (verifyValues({ prop: 'font-size', value: sizePart, selector }, silentVerify)) {
|
|
881
|
+
sizeIdx = i
|
|
882
|
+
cssMap.push({ prop: 'fontSize', value: sizePart })
|
|
883
|
+
if (lhPart) lineHeight = lhPart
|
|
884
|
+
break
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (sizeIdx === -1) {
|
|
888
|
+
// 缺必填 font-size → 整条声明丢弃
|
|
889
|
+
error(`Value of [${prop}:${value}] in ${selector} is missing required <font-size>, please check again!`)
|
|
890
|
+
return false
|
|
891
|
+
}
|
|
892
|
+
// 2. 前导段(font-size 之前):识别 font-style / font-variant(small-caps) / font-weight,顺序不敏感
|
|
893
|
+
for (let i = 0; i < sizeIdx; i++) {
|
|
894
|
+
const t = tokens[i]
|
|
895
|
+
if (t === 'normal') continue // 默认值跳过
|
|
896
|
+
if (verifyValues({ prop: 'font-style', value: t, selector }, silentVerify)) {
|
|
897
|
+
cssMap.push({ prop: 'fontStyle', value: t })
|
|
898
|
+
} else if (t === 'small-caps') {
|
|
899
|
+
// CSS font 简写的 variant 槽位仅 <font-variant-css2>(normal | small-caps);
|
|
900
|
+
// 字符串透传,RN processFontVariant 会 split 归一为数组,与 font-variant 长属性同口径
|
|
901
|
+
cssMap.push({ prop: 'fontVariant', value: t })
|
|
902
|
+
} else if (verifyValues({ prop: 'font-weight', value: t, selector }, silentVerify)) {
|
|
903
|
+
cssMap.push({ prop: 'fontWeight', value: t })
|
|
904
|
+
} else {
|
|
905
|
+
// font-stretch / 数字型 font-variant-numeric / system 关键字等 → 非必填槽位,warn 并忽略该 token、保留其余
|
|
906
|
+
warn(`Value of [${prop}:${value}] in ${selector}: token [${t}] is not supported (only font-style / small-caps / font-weight are valid before <font-size>), ignored.`)
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
// 3. line-height(数值复用 formatLineHeight 口径换算为百分比)
|
|
910
|
+
if (lineHeight !== undefined) {
|
|
911
|
+
const lh = formatLineHeight({ prop: 'line-height', value: lineHeight, selector })
|
|
912
|
+
if (lh) cssMap.push(lh)
|
|
913
|
+
}
|
|
914
|
+
// 4. font-family(font-size 之后剩余部分)
|
|
915
|
+
const familyStr = tokens.slice(sizeIdx + 1).join(' ').trim()
|
|
916
|
+
if (!familyStr) {
|
|
917
|
+
// 缺必填 font-family → 整条声明丢弃
|
|
918
|
+
error(`Value of [${prop}:${value}] in ${selector} is missing required <font-family>, please check again!`)
|
|
919
|
+
return false
|
|
645
920
|
}
|
|
921
|
+
const family = formatFontFamily({ prop: 'font-family', value: familyStr, selector })
|
|
922
|
+
if (family) cssMap.push({ prop: 'fontFamily', value: family.value })
|
|
923
|
+
return cssMap
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// border / outline 简写只负责按值类型展开和补齐缺省值;
|
|
927
|
+
// none 清除语义不在编译期折叠,统一保留到运行时最终处理。
|
|
928
|
+
const formatBorder = ({ prop, value, selector }, { mode }) => {
|
|
929
|
+
value = value.trim()
|
|
930
|
+
return formatUnorderedAbbreviation({ prop, value, selector }, { mode })
|
|
646
931
|
}
|
|
647
932
|
|
|
648
933
|
return {
|
|
@@ -650,12 +935,12 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
650
935
|
rules: [
|
|
651
936
|
{ // 背景相关属性的处理
|
|
652
937
|
test: /^(background|background-image|background-size|background-position)$/,
|
|
653
|
-
ios:
|
|
654
|
-
android:
|
|
655
|
-
harmony:
|
|
938
|
+
ios: formatBackground,
|
|
939
|
+
android: formatBackground,
|
|
940
|
+
harmony: formatBackground
|
|
656
941
|
},
|
|
657
|
-
{ // margin padding
|
|
658
|
-
test: /^(margin|padding|border-radius|border-width|border-color)$/,
|
|
942
|
+
{ // margin padding 内外边距 / gap / inset 的处理(统一走复合值展开)
|
|
943
|
+
test: /^(margin|padding|border-radius|border-width|border-color|gap|inset)$/,
|
|
659
944
|
ios: formatCompositeVal,
|
|
660
945
|
android: formatCompositeVal,
|
|
661
946
|
harmony: formatCompositeVal
|
|
@@ -684,14 +969,20 @@ module.exports = function getSpec({ warn, error }) {
|
|
|
684
969
|
android: formatFontFamily,
|
|
685
970
|
harmony: formatFontFamily
|
|
686
971
|
},
|
|
972
|
+
{ // font 简写专用处理:必须置于通用 AbbreviationMap 匹配规则之前
|
|
973
|
+
test: 'font',
|
|
974
|
+
ios: formatFont,
|
|
975
|
+
android: formatFont,
|
|
976
|
+
harmony: formatFont
|
|
977
|
+
},
|
|
687
978
|
// {
|
|
688
979
|
// test: 'box-shadow',
|
|
689
980
|
// ios: formatBoxShadow,
|
|
690
981
|
// android: formatBoxShadow,
|
|
691
982
|
// harmony: formatBoxShadow
|
|
692
983
|
// },
|
|
693
|
-
{
|
|
694
|
-
test:
|
|
984
|
+
{ // border / outline 简写共享 formatBorder(按值类型展开 + 缺省补齐;none 清除语义留到运行时处理)
|
|
985
|
+
test: /^(border|border-left|border-right|border-top|border-bottom|outline)$/,
|
|
695
986
|
ios: formatBorder,
|
|
696
987
|
android: formatBorder,
|
|
697
988
|
harmony: formatBorder
|