@mpxjs/webpack-plugin 2.9.62 → 2.9.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/lib/index.js +1 -3
  2. package/lib/platform/style/wx/index.js +67 -53
  3. package/lib/react/processStyles.js +1 -0
  4. package/lib/react/processTemplate.js +2 -3
  5. package/lib/react/style-helper.js +12 -7
  6. package/lib/runtime/components/react/context.ts +9 -7
  7. package/lib/runtime/components/react/dist/context.js +1 -0
  8. package/lib/runtime/components/react/dist/getInnerListeners.js +12 -1
  9. package/lib/runtime/components/react/dist/mpx-button.jsx +52 -74
  10. package/lib/runtime/components/react/dist/mpx-checkbox-group.jsx +19 -18
  11. package/lib/runtime/components/react/dist/mpx-checkbox.jsx +28 -41
  12. package/lib/runtime/components/react/dist/mpx-form.jsx +16 -14
  13. package/lib/runtime/components/react/dist/mpx-icon.jsx +14 -17
  14. package/lib/runtime/components/react/dist/mpx-image/index.jsx +34 -33
  15. package/lib/runtime/components/react/dist/mpx-image/svg.jsx +3 -1
  16. package/lib/runtime/components/react/dist/mpx-input.jsx +35 -31
  17. package/lib/runtime/components/react/dist/mpx-label.jsx +29 -37
  18. package/lib/runtime/components/react/dist/mpx-movable-area.jsx +13 -18
  19. package/lib/runtime/components/react/dist/mpx-movable-view.jsx +8 -8
  20. package/lib/runtime/components/react/dist/mpx-picker/index.jsx +9 -9
  21. package/lib/runtime/components/react/dist/mpx-picker/multiSelector.jsx +7 -4
  22. package/lib/runtime/components/react/dist/mpx-picker/region.jsx +11 -7
  23. package/lib/runtime/components/react/dist/mpx-picker/selector.jsx +1 -1
  24. package/lib/runtime/components/react/dist/mpx-picker/time.jsx +18 -18
  25. package/lib/runtime/components/react/dist/mpx-picker-view-column.jsx +102 -10
  26. package/lib/runtime/components/react/dist/mpx-picker-view.jsx +147 -53
  27. package/lib/runtime/components/react/dist/mpx-radio-group.jsx +19 -18
  28. package/lib/runtime/components/react/dist/mpx-radio.jsx +28 -43
  29. package/lib/runtime/components/react/dist/mpx-root-portal.jsx +8 -4
  30. package/lib/runtime/components/react/dist/mpx-scroll-view.jsx +33 -26
  31. package/lib/runtime/components/react/dist/mpx-swiper/carouse.jsx +139 -74
  32. package/lib/runtime/components/react/dist/mpx-swiper/index.jsx +14 -6
  33. package/lib/runtime/components/react/dist/mpx-swiper-item.jsx +19 -11
  34. package/lib/runtime/components/react/dist/mpx-switch.jsx +17 -14
  35. package/lib/runtime/components/react/dist/mpx-text.jsx +19 -35
  36. package/lib/runtime/components/react/dist/mpx-textarea.jsx +1 -1
  37. package/lib/runtime/components/react/dist/mpx-view.jsx +284 -209
  38. package/lib/runtime/components/react/dist/mpx-web-view.jsx +8 -5
  39. package/lib/runtime/components/react/dist/parser.js +218 -0
  40. package/lib/runtime/components/react/dist/utils.jsx +433 -0
  41. package/lib/runtime/components/react/getInnerListeners.ts +18 -8
  42. package/lib/runtime/components/react/mpx-button.tsx +81 -91
  43. package/lib/runtime/components/react/mpx-checkbox-group.tsx +48 -43
  44. package/lib/runtime/components/react/mpx-checkbox.tsx +52 -63
  45. package/lib/runtime/components/react/mpx-form.tsx +49 -21
  46. package/lib/runtime/components/react/mpx-icon.tsx +30 -27
  47. package/lib/runtime/components/react/mpx-image/index.tsx +52 -46
  48. package/lib/runtime/components/react/mpx-image/svg.tsx +5 -3
  49. package/lib/runtime/components/react/mpx-input.tsx +58 -38
  50. package/lib/runtime/components/react/mpx-label.tsx +54 -59
  51. package/lib/runtime/components/react/mpx-movable-area.tsx +38 -24
  52. package/lib/runtime/components/react/mpx-movable-view.tsx +27 -28
  53. package/lib/runtime/components/react/mpx-navigator.tsx +2 -2
  54. package/lib/runtime/components/react/mpx-picker/date.tsx +2 -3
  55. package/lib/runtime/components/react/mpx-picker/index.tsx +10 -10
  56. package/lib/runtime/components/react/mpx-picker/multiSelector.tsx +15 -12
  57. package/lib/runtime/components/react/mpx-picker/region.tsx +21 -18
  58. package/lib/runtime/components/react/mpx-picker/selector.tsx +5 -6
  59. package/lib/runtime/components/react/mpx-picker/time.tsx +25 -29
  60. package/lib/runtime/components/react/mpx-picker/type.ts +1 -1
  61. package/lib/runtime/components/react/mpx-picker-view-column.tsx +148 -20
  62. package/lib/runtime/components/react/mpx-picker-view.tsx +179 -63
  63. package/lib/runtime/components/react/mpx-radio-group.tsx +50 -47
  64. package/lib/runtime/components/react/mpx-radio.tsx +56 -72
  65. package/lib/runtime/components/react/mpx-root-portal.tsx +10 -8
  66. package/lib/runtime/components/react/mpx-scroll-view.tsx +133 -103
  67. package/lib/runtime/components/react/mpx-swiper/carouse.tsx +174 -96
  68. package/lib/runtime/components/react/mpx-swiper/index.tsx +18 -9
  69. package/lib/runtime/components/react/mpx-swiper/type.ts +16 -5
  70. package/lib/runtime/components/react/mpx-swiper-item.tsx +46 -13
  71. package/lib/runtime/components/react/mpx-switch.tsx +44 -23
  72. package/lib/runtime/components/react/mpx-text.tsx +37 -45
  73. package/lib/runtime/components/react/mpx-textarea.tsx +1 -1
  74. package/lib/runtime/components/react/mpx-view.tsx +388 -240
  75. package/lib/runtime/components/react/mpx-web-view.tsx +19 -20
  76. package/lib/runtime/components/react/parser.ts +245 -0
  77. package/lib/runtime/components/react/types/common.ts +4 -4
  78. package/lib/runtime/components/react/types/global.d.ts +14 -2
  79. package/lib/runtime/components/react/useNodesRef.ts +1 -2
  80. package/lib/runtime/components/react/utils.tsx +505 -0
  81. package/lib/template-compiler/compiler.js +28 -20
  82. package/lib/template-compiler/gen-node-react.js +1 -3
  83. package/lib/web/processStyles.js +2 -5
  84. package/package.json +6 -4
  85. package/lib/runtime/components/react/dist/utils.js +0 -148
  86. package/lib/runtime/components/react/utils.ts +0 -170
@@ -1,14 +1,15 @@
1
1
  import { forwardRef, useEffect } from 'react';
2
- // @ts-ignore
3
- import { noop } from '@mpxjs/utils';
2
+ import { noop, warn } from '@mpxjs/utils';
4
3
  import { Portal } from '@ant-design/react-native';
5
4
  import { getCustomEvent } from './getInnerListeners';
6
5
  import { promisify, redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy';
7
- // @ts-ignore
8
6
  import { WebView } from 'react-native-webview';
9
7
  import useNodesRef from './useNodesRef';
10
8
  const _WebView = forwardRef((props, ref) => {
11
9
  const { src, bindmessage = noop, bindload = noop, binderror = noop } = props;
10
+ if (props.style) {
11
+ warn('The web-view component does not support the style prop.');
12
+ }
12
13
  const defaultWebViewStyle = {
13
14
  position: 'absolute',
14
15
  left: 0,
@@ -56,12 +57,14 @@ const _WebView = forwardRef((props, ref) => {
56
57
  binderror(result);
57
58
  };
58
59
  const _message = function (res) {
59
- let data;
60
+ let data = {};
60
61
  let asyncCallback;
61
62
  const navObj = promisify({ redirectTo, navigateTo, navigateBack, reLaunch, switchTab });
62
63
  try {
63
64
  const nativeEventData = res.nativeEvent?.data;
64
- data = JSON.parse(nativeEventData);
65
+ if (typeof nativeEventData === 'string') {
66
+ data = JSON.parse(nativeEventData);
67
+ }
65
68
  }
66
69
  catch (e) {
67
70
  data = {};
@@ -0,0 +1,218 @@
1
+ export class ExpressionParser {
2
+ tokens;
3
+ formatter;
4
+ functions;
5
+ current;
6
+ constructor(input, formatter = val => parseFloat(val), functions = {}) {
7
+ this.tokens = this.tokenize(input);
8
+ this.formatter = formatter;
9
+ this.functions = functions;
10
+ this.current = 0;
11
+ }
12
+ tokenize(input) {
13
+ const tokens = [];
14
+ const regex = /(\d+\.?\d*(?:px|rpx|%|vw|vh)?|[+\-*/(),]|\b[a-zA-Z_][a-zA-Z0-9_]*\b)/g;
15
+ let match;
16
+ while ((match = regex.exec(input))) {
17
+ if (/^\d+\.?\d*(?:px|rpx|%|vw|vh)?$/.test(match[0])) {
18
+ const lastToken = tokens[tokens.length - 1];
19
+ const last2Token = tokens[tokens.length - 2];
20
+ if (lastToken?.type === '-' && (!last2Token || /^[+\-*/(,]$/.test(last2Token?.type))) {
21
+ tokens.pop();
22
+ tokens.push({
23
+ type: 'NUMBER',
24
+ value: '-' + match[0]
25
+ });
26
+ }
27
+ else {
28
+ tokens.push({
29
+ type: 'NUMBER',
30
+ value: match[0]
31
+ });
32
+ }
33
+ }
34
+ else {
35
+ tokens.push({
36
+ type: match[0],
37
+ value: match[0]
38
+ });
39
+ }
40
+ }
41
+ return tokens;
42
+ }
43
+ parse() {
44
+ return this.expression();
45
+ }
46
+ expression() {
47
+ let node = this.term();
48
+ while (this.current < this.tokens.length &&
49
+ (this.tokens[this.current].type === '+' || this.tokens[this.current].type === '-')) {
50
+ const operator = this.tokens[this.current].type;
51
+ this.current++;
52
+ const right = this.term();
53
+ node = this.applyOperator(operator, node, right);
54
+ }
55
+ return node;
56
+ }
57
+ term() {
58
+ let node = this.factor();
59
+ while (this.current < this.tokens.length &&
60
+ (this.tokens[this.current].type === '*' || this.tokens[this.current].type === '/')) {
61
+ const operator = this.tokens[this.current].type;
62
+ this.current++;
63
+ const right = this.factor();
64
+ node = this.applyOperator(operator, node, right);
65
+ }
66
+ return node;
67
+ }
68
+ factor() {
69
+ const token = this.tokens[this.current];
70
+ if (token.type === 'NUMBER') {
71
+ this.current++;
72
+ const numericValue = this.formatter(token.value);
73
+ return { type: 'NUMBER', value: numericValue };
74
+ }
75
+ else if (token.type === '(') {
76
+ this.current++;
77
+ const node = this.expression();
78
+ if (this.tokens[this.current].type !== ')') {
79
+ throw new Error('Expected closing parenthesis');
80
+ }
81
+ this.current++;
82
+ return node;
83
+ }
84
+ else if (this.functions[token.type]) {
85
+ this.current++;
86
+ if (this.tokens[this.current].type !== '(') {
87
+ throw new Error('Expected opening parenthesis after function');
88
+ }
89
+ this.current++;
90
+ const args = this.parseArguments();
91
+ if (this.tokens[this.current].type !== ')') {
92
+ throw new Error('Expected closing parenthesis');
93
+ }
94
+ this.current++;
95
+ return this.applyFunction(token.type, args);
96
+ }
97
+ throw new Error(`Unexpected token: ${token.type}`);
98
+ }
99
+ parseArguments() {
100
+ const args = [];
101
+ while (this.current < this.tokens.length && this.tokens[this.current].type !== ')') {
102
+ args.push(this.expression());
103
+ if (this.tokens[this.current].type === ',') {
104
+ this.current++;
105
+ }
106
+ }
107
+ return args;
108
+ }
109
+ applyOperator(operator, left, right) {
110
+ const leftVal = left.value;
111
+ const rightVal = right.value;
112
+ let result;
113
+ switch (operator) {
114
+ case '+':
115
+ result = leftVal + rightVal;
116
+ break;
117
+ case '-':
118
+ result = leftVal - rightVal;
119
+ break;
120
+ case '*':
121
+ result = leftVal * rightVal;
122
+ break;
123
+ case '/':
124
+ result = leftVal / rightVal;
125
+ break;
126
+ default: throw new Error(`Unknown operator: ${operator}`);
127
+ }
128
+ return { type: 'NUMBER', value: result };
129
+ }
130
+ applyFunction(func, args) {
131
+ if (args.some(arg => arg.type !== 'NUMBER')) {
132
+ throw new Error('Function arguments must be numbers');
133
+ }
134
+ const numericArgs = args.map(arg => arg.value);
135
+ if (this.functions[func]) {
136
+ return { type: 'NUMBER', value: this.functions[func].apply(null, numericArgs) };
137
+ }
138
+ else {
139
+ throw new Error(`Unknown function: ${func}`);
140
+ }
141
+ }
142
+ }
143
+ export function parseFunc(str, funcName) {
144
+ const regex = new RegExp(`${funcName}\\(`, 'g');
145
+ const result = [];
146
+ let match;
147
+ while ((match = regex.exec(str)) !== null) {
148
+ const start = match.index;
149
+ let i = start + funcName.length + 1;
150
+ let depth = 1;
151
+ const args = [];
152
+ let arg = '';
153
+ while (depth && i < str.length) {
154
+ if (depth === 1 && (str[i] === ',' || str[i] === ')')) {
155
+ args.push(arg.trim());
156
+ arg = '';
157
+ }
158
+ else {
159
+ arg += str[i];
160
+ }
161
+ switch (str[i]) {
162
+ case '(':
163
+ depth++;
164
+ break;
165
+ case ')':
166
+ depth--;
167
+ break;
168
+ default:
169
+ // Do nothing
170
+ }
171
+ i++;
172
+ }
173
+ const end = regex.lastIndex = i;
174
+ result.push({
175
+ start,
176
+ end,
177
+ args
178
+ });
179
+ }
180
+ return result;
181
+ }
182
+ export class ReplaceSource {
183
+ _source;
184
+ _replacements;
185
+ constructor(source) {
186
+ this._source = source;
187
+ this._replacements = [];
188
+ }
189
+ replace(start, end, content) {
190
+ this._replacements.push({ start, end, content });
191
+ }
192
+ source() {
193
+ if (this._replacements.length === 0) {
194
+ return this._source;
195
+ }
196
+ let current = this._source;
197
+ let pos = 0;
198
+ const result = [];
199
+ for (const replacement of this._replacements) {
200
+ const start = Math.floor(replacement.start);
201
+ const end = Math.floor(replacement.end) + 1;
202
+ if (pos < start) {
203
+ const offset = start - pos;
204
+ result.push(current.slice(0, offset));
205
+ current = current.slice(offset);
206
+ pos = start;
207
+ }
208
+ result.push(replacement.content);
209
+ if (pos < end) {
210
+ const offset = end - pos;
211
+ current = current.slice(offset);
212
+ pos = end;
213
+ }
214
+ }
215
+ result.push(current);
216
+ return result.join('');
217
+ }
218
+ }
@@ -0,0 +1,433 @@
1
+ import { useEffect, useRef, isValidElement, useContext, useState, Children, cloneElement } from 'react';
2
+ import { Dimensions, StyleSheet } from 'react-native';
3
+ import { isObject, hasOwn, diffAndCloneA, error, warn } from '@mpxjs/utils';
4
+ import { VarContext } from './context';
5
+ import { ExpressionParser, parseFunc, ReplaceSource } from './parser';
6
+ export const TEXT_STYLE_REGEX = /color|font.*|text.*|letterSpacing|lineHeight|includeFontPadding|writingDirection/;
7
+ export const PERCENT_REGEX = /^\s*-?\d+(\.\d+)?%\s*$/;
8
+ export const URL_REGEX = /^\s*url\(["']?(.*?)["']?\)\s*$/;
9
+ export const BACKGROUND_REGEX = /^background(Image|Size|Repeat|Position)$/;
10
+ export const TEXT_PROPS_REGEX = /ellipsizeMode|numberOfLines/;
11
+ export const DEFAULT_FONT_SIZE = 16;
12
+ export const DEFAULT_UNLAY_STYLE = {
13
+ opacity: 0
14
+ };
15
+ export function rpx(value) {
16
+ const { width } = Dimensions.get('screen');
17
+ // rn 单位 dp = 1(css)px = 1 物理像素 * pixelRatio(像素比)
18
+ // px = rpx * (750 / 屏幕宽度)
19
+ return value * width / 750;
20
+ }
21
+ const rpxRegExp = /^\s*(-?\d+(\.\d+)?)rpx\s*$/;
22
+ const pxRegExp = /^\s*(-?\d+(\.\d+)?)(px)?\s*$/;
23
+ const hairlineRegExp = /^\s*hairlineWidth\s*$/;
24
+ const varDecRegExp = /^--.*/;
25
+ const varUseRegExp = /var\(/;
26
+ const calcUseRegExp = /calc\(/;
27
+ export function omit(obj, fields) {
28
+ const shallowCopy = Object.assign({}, obj);
29
+ for (let i = 0; i < fields.length; i += 1) {
30
+ const key = fields[i];
31
+ delete shallowCopy[key];
32
+ }
33
+ return shallowCopy;
34
+ }
35
+ /**
36
+ * 用法等同于 useEffect,但是会忽略首次执行,只在依赖更新时执行
37
+ */
38
+ export const useUpdateEffect = (effect, deps) => {
39
+ const isMounted = useRef(false);
40
+ // for react-refresh
41
+ useEffect(() => {
42
+ return () => {
43
+ isMounted.current = false;
44
+ };
45
+ }, []);
46
+ useEffect(() => {
47
+ if (!isMounted.current) {
48
+ isMounted.current = true;
49
+ }
50
+ else {
51
+ return effect();
52
+ }
53
+ }, deps);
54
+ };
55
+ /**
56
+ * 解析行内样式
57
+ * @param inlineStyle
58
+ * @returns
59
+ */
60
+ export const parseInlineStyle = (inlineStyle = '') => {
61
+ return inlineStyle.split(';').reduce((styleObj, style) => {
62
+ const [k, v, ...rest] = style.split(':');
63
+ if (rest.length || !v || !k)
64
+ return styleObj;
65
+ const key = k.trim().replace(/-./g, c => c.substring(1).toUpperCase());
66
+ return Object.assign(styleObj, { [key]: v.trim() });
67
+ }, {});
68
+ };
69
+ export const parseUrl = (cssUrl = '') => {
70
+ if (!cssUrl)
71
+ return;
72
+ const match = cssUrl.match(URL_REGEX);
73
+ return match?.[1];
74
+ };
75
+ export const getRestProps = (transferProps = {}, originProps = {}, deletePropsKey = []) => {
76
+ return {
77
+ ...transferProps,
78
+ ...omit(originProps, deletePropsKey)
79
+ };
80
+ };
81
+ export function isText(ele) {
82
+ if (isValidElement(ele)) {
83
+ const displayName = ele.type?.displayName;
84
+ return displayName === 'mpx-text' || displayName === 'Text';
85
+ }
86
+ return false;
87
+ }
88
+ export function isEmbedded(ele) {
89
+ if (isValidElement(ele)) {
90
+ const displayName = ele.type?.displayName || '';
91
+ return ['mpx-checkbox', 'mpx-radio', 'mpx-switch'].includes(displayName);
92
+ }
93
+ return false;
94
+ }
95
+ export function every(children, callback) {
96
+ const childrenArray = Array.isArray(children) ? children : [children];
97
+ return childrenArray.every((child) => callback(child));
98
+ }
99
+ export function groupBy(obj, callback, group = {}) {
100
+ Object.entries(obj).forEach(([key, val]) => {
101
+ const groupKey = callback(key, val);
102
+ group[groupKey] = group[groupKey] || {};
103
+ group[groupKey][key] = val;
104
+ });
105
+ return group;
106
+ }
107
+ export function splitStyle(styleObj) {
108
+ return groupBy(styleObj, (key) => {
109
+ if (TEXT_STYLE_REGEX.test(key)) {
110
+ return 'textStyle';
111
+ }
112
+ else if (BACKGROUND_REGEX.test(key)) {
113
+ return 'backgroundStyle';
114
+ }
115
+ else {
116
+ return 'innerStyle';
117
+ }
118
+ });
119
+ }
120
+ const selfPercentRule = {
121
+ translateX: 'width',
122
+ translateY: 'height',
123
+ borderTopLeftRadius: 'width',
124
+ borderBottomLeftRadius: 'width',
125
+ borderBottomRightRadius: 'width',
126
+ borderTopRightRadius: 'width',
127
+ borderRadius: 'width'
128
+ };
129
+ const parentHeightPercentRule = {
130
+ height: true,
131
+ top: true,
132
+ bottom: true
133
+ };
134
+ // todo calc时处理角度和时间等单位
135
+ function formatValue(value) {
136
+ let matched;
137
+ if ((matched = pxRegExp.exec(value))) {
138
+ return +matched[1];
139
+ }
140
+ else if ((matched = rpxRegExp.exec(value))) {
141
+ return rpx(+matched[1]);
142
+ }
143
+ else if (hairlineRegExp.test(value)) {
144
+ return StyleSheet.hairlineWidth;
145
+ }
146
+ return value;
147
+ }
148
+ function resolvePercent(value, key, percentConfig) {
149
+ if (!(typeof value === 'string' && PERCENT_REGEX.test(value)))
150
+ return value;
151
+ let base;
152
+ let reason;
153
+ if (key === 'fontSize') {
154
+ base = percentConfig.parentFontSize;
155
+ reason = 'parent-font-size';
156
+ }
157
+ else if (key === 'lineHeight') {
158
+ base = resolvePercent(percentConfig.fontSize, 'fontSize', percentConfig);
159
+ reason = 'font-size';
160
+ }
161
+ else if (selfPercentRule[key]) {
162
+ base = percentConfig[selfPercentRule[key]];
163
+ reason = selfPercentRule[key];
164
+ }
165
+ else if (parentHeightPercentRule[key]) {
166
+ base = percentConfig.parentHeight;
167
+ reason = 'parent-height';
168
+ }
169
+ else {
170
+ base = percentConfig.parentWidth;
171
+ reason = 'parent-width';
172
+ }
173
+ if (typeof base !== 'number') {
174
+ error(`[${key}] can not contain % unit unless you set [${reason}] with a number for the percent calculation.`);
175
+ return value;
176
+ }
177
+ else {
178
+ return parseFloat(value) / 100 * base;
179
+ }
180
+ }
181
+ function transformPercent(styleObj, percentKeyPaths, percentConfig) {
182
+ percentKeyPaths.forEach((percentKeyPath) => {
183
+ setStyle(styleObj, percentKeyPath, ({ target, key, value }) => {
184
+ target[key] = resolvePercent(value, key, percentConfig);
185
+ });
186
+ });
187
+ }
188
+ function resolveVar(input, varContext) {
189
+ const parsed = parseFunc(input, 'var');
190
+ const replaced = new ReplaceSource(input);
191
+ parsed.forEach(({ start, end, args }) => {
192
+ const varName = args[0];
193
+ const fallback = args[1] || '';
194
+ let varValue = hasOwn(varContext, varName) ? varContext[varName] : fallback;
195
+ if (varUseRegExp.test(varValue)) {
196
+ varValue = '' + resolveVar(varValue, varContext);
197
+ }
198
+ else {
199
+ varValue = '' + formatValue(varValue);
200
+ }
201
+ replaced.replace(start, end - 1, varValue);
202
+ });
203
+ return formatValue(replaced.source());
204
+ }
205
+ function transformVar(styleObj, varKeyPaths, varContext) {
206
+ varKeyPaths.forEach((varKeyPath) => {
207
+ setStyle(styleObj, varKeyPath, ({ target, key, value }) => {
208
+ target[key] = resolveVar(value, varContext);
209
+ });
210
+ });
211
+ }
212
+ function transformCalc(styleObj, calcKeyPaths, formatter) {
213
+ calcKeyPaths.forEach((calcKeyPath) => {
214
+ setStyle(styleObj, calcKeyPath, ({ target, key, value }) => {
215
+ const parsed = parseFunc(value, 'calc');
216
+ const replaced = new ReplaceSource(value);
217
+ parsed.forEach(({ start, end, args }) => {
218
+ const exp = args[0];
219
+ try {
220
+ const result = new ExpressionParser(exp, (value) => {
221
+ return formatter(value, key);
222
+ }).parse();
223
+ replaced.replace(start, end - 1, '' + result.value);
224
+ }
225
+ catch (e) {
226
+ error(`calc(${exp}) parse error.`, undefined, e);
227
+ }
228
+ });
229
+ target[key] = formatValue(replaced.source());
230
+ });
231
+ });
232
+ }
233
+ export function useTransformStyle(styleObj = {}, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight }) {
234
+ const varStyle = {};
235
+ const normalStyle = {};
236
+ let hasVarDec = false;
237
+ let hasVarUse = false;
238
+ let hasSelfPercent = false;
239
+ const varKeyPaths = [];
240
+ const percentKeyPaths = [];
241
+ const calcKeyPaths = [];
242
+ const [width, setWidth] = useState(0);
243
+ const [height, setHeight] = useState(0);
244
+ function varVisitor({ key, value, keyPath }) {
245
+ if (keyPath.length === 1) {
246
+ if (varDecRegExp.test(key)) {
247
+ hasVarDec = true;
248
+ varStyle[key] = value;
249
+ }
250
+ else {
251
+ // clone对象避免set值时改写到props
252
+ normalStyle[key] = isObject(value) ? diffAndCloneA(value).clone : value;
253
+ }
254
+ }
255
+ // 对于var定义中使用的var无需替换值,可以通过resolveVar递归解析出值
256
+ if (!varDecRegExp.test(key) && varUseRegExp.test(value)) {
257
+ hasVarUse = true;
258
+ varKeyPaths.push(keyPath.slice());
259
+ }
260
+ }
261
+ // traverse var
262
+ traverseStyle(styleObj, [varVisitor]);
263
+ hasVarDec = hasVarDec || !!externalVarContext;
264
+ enableVar = enableVar || hasVarDec || hasVarUse;
265
+ const enableVarRef = useRef(enableVar);
266
+ if (enableVarRef.current !== enableVar) {
267
+ error('css variable use/declare should be stable in the component lifecycle, or you can set [enable-var] with true.');
268
+ }
269
+ // apply var
270
+ const varContextRef = useRef({});
271
+ if (enableVarRef.current) {
272
+ const varContext = useContext(VarContext);
273
+ const newVarContext = Object.assign({}, varContext, externalVarContext, varStyle);
274
+ // 缓存比较newVarContext是否发生变化
275
+ if (diffAndCloneA(varContextRef.current, newVarContext).diff) {
276
+ varContextRef.current = newVarContext;
277
+ }
278
+ transformVar(normalStyle, varKeyPaths, varContextRef.current);
279
+ }
280
+ function calcVisitor({ value, keyPath }) {
281
+ if (calcUseRegExp.test(value)) {
282
+ calcKeyPaths.push(keyPath.slice());
283
+ }
284
+ }
285
+ function percentVisitor({ key, value, keyPath }) {
286
+ if (hasOwn(selfPercentRule, key) && PERCENT_REGEX.test(value)) {
287
+ hasSelfPercent = true;
288
+ percentKeyPaths.push(keyPath.slice());
289
+ }
290
+ else if (key === 'fontSize' || key === 'lineHeight') {
291
+ percentKeyPaths.push(keyPath.slice());
292
+ }
293
+ }
294
+ // traverse calc & percent
295
+ traverseStyle(normalStyle, [percentVisitor, calcVisitor]);
296
+ const percentConfig = {
297
+ width,
298
+ height,
299
+ fontSize: normalStyle.fontSize,
300
+ parentWidth,
301
+ parentHeight,
302
+ parentFontSize
303
+ };
304
+ // apply percent
305
+ transformPercent(normalStyle, percentKeyPaths, percentConfig);
306
+ // apply calc
307
+ transformCalc(normalStyle, calcKeyPaths, (value, key) => {
308
+ if (PERCENT_REGEX.test(value)) {
309
+ const resolved = resolvePercent(value, key, percentConfig);
310
+ return typeof resolved === 'number' ? resolved : 0;
311
+ }
312
+ else {
313
+ const formatted = formatValue(value);
314
+ if (typeof formatted === 'number') {
315
+ return formatted;
316
+ }
317
+ else {
318
+ warn('calc() only support number, px, rpx, % temporarily.');
319
+ return 0;
320
+ }
321
+ }
322
+ });
323
+ return {
324
+ normalStyle,
325
+ hasSelfPercent,
326
+ hasVarDec,
327
+ enableVarRef,
328
+ varContextRef,
329
+ setWidth,
330
+ setHeight
331
+ };
332
+ }
333
+ export function traverseStyle(styleObj, visitors) {
334
+ const keyPath = [];
335
+ function traverse(target) {
336
+ if (Array.isArray(target)) {
337
+ target.forEach((value, index) => {
338
+ const key = String(index);
339
+ keyPath.push(key);
340
+ visitors.forEach(visitor => visitor({
341
+ target,
342
+ key,
343
+ value,
344
+ keyPath
345
+ }));
346
+ traverse(value);
347
+ keyPath.pop();
348
+ });
349
+ }
350
+ else if (isObject(target)) {
351
+ Object.entries(target).forEach(([key, value]) => {
352
+ keyPath.push(key);
353
+ visitors.forEach(visitor => visitor({ target, key, value, keyPath }));
354
+ traverse(value);
355
+ keyPath.pop();
356
+ });
357
+ }
358
+ }
359
+ traverse(styleObj);
360
+ }
361
+ export function setStyle(styleObj, keyPath, setter, needClone = false) {
362
+ let target = styleObj;
363
+ const firstKey = keyPath[0];
364
+ const lastKey = keyPath[keyPath.length - 1];
365
+ if (needClone)
366
+ target[firstKey] = diffAndCloneA(target[firstKey]).clone;
367
+ for (let i = 0; i < keyPath.length - 1; i++) {
368
+ target = target[keyPath[i]];
369
+ if (!target)
370
+ return;
371
+ }
372
+ setter({
373
+ target,
374
+ key: lastKey,
375
+ value: target[lastKey],
376
+ keyPath
377
+ });
378
+ }
379
+ export function splitProps(props) {
380
+ return groupBy(props, (key) => {
381
+ if (TEXT_PROPS_REGEX.test(key)) {
382
+ return 'textProps';
383
+ }
384
+ else {
385
+ return 'innerProps';
386
+ }
387
+ });
388
+ }
389
+ export const useLayout = ({ props, hasSelfPercent, setWidth, setHeight, onLayout, nodeRef }) => {
390
+ const layoutRef = useRef({});
391
+ const hasLayoutRef = useRef(false);
392
+ const layoutStyle = !hasLayoutRef.current && hasSelfPercent ? DEFAULT_UNLAY_STYLE : {};
393
+ const layoutProps = {};
394
+ const enableOffset = props['enable-offset'];
395
+ if (hasSelfPercent || onLayout || enableOffset) {
396
+ layoutProps.onLayout = (e) => {
397
+ hasLayoutRef.current = true;
398
+ if (hasSelfPercent) {
399
+ const { width, height } = e?.nativeEvent?.layout || {};
400
+ setWidth(width || 0);
401
+ setHeight(height || 0);
402
+ }
403
+ if (enableOffset) {
404
+ nodeRef.current?.measure((x, y, width, height, offsetLeft, offsetTop) => {
405
+ layoutRef.current = { x, y, width, height, offsetLeft, offsetTop };
406
+ });
407
+ }
408
+ onLayout && onLayout(e);
409
+ props.onLayout && props.onLayout(e);
410
+ };
411
+ }
412
+ return {
413
+ layoutRef,
414
+ layoutStyle,
415
+ layoutProps
416
+ };
417
+ };
418
+ export function wrapChildren(props = {}, { hasVarDec, varContext, textStyle, textProps }) {
419
+ let { children } = props;
420
+ if (textStyle || textProps) {
421
+ children = Children.map(children, (child) => {
422
+ if (isText(child)) {
423
+ const style = { ...textStyle, ...child.props.style };
424
+ return cloneElement(child, { ...textProps, style });
425
+ }
426
+ return child;
427
+ });
428
+ }
429
+ if (hasVarDec && varContext) {
430
+ children = <VarContext.Provider value={varContext} key='varContextWrap'>{children}</VarContext.Provider>;
431
+ }
432
+ return children;
433
+ }