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