styimat 1.3.0 → 1.4.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.
Files changed (3) hide show
  1. package/package.json +2 -2
  2. package/styimat.js +333 -27
  3. package/styimat.min.js +153 -22
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "styimat",
3
- "version": "1.3.0",
4
- "description": "A powerful CSS variable preprocessor with nested selectors, Lab/LCH color spaces conversion and modern color features.",
3
+ "version": "1.4.0",
4
+ "description": "一个高效的CSS变量预处理库,支持Lab/LCH颜色空间自动转换、嵌套选择器和Display P3广色域,让现代CSS开发更简洁强大。",
5
5
  "keywords": [
6
6
  "css",
7
7
  "preprocessor",
package/styimat.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * 支持 Lab 和 LCH 颜色空间转换为 RGB(CSS标准格式)
5
5
  * 支持 lab# 和 lch# 十六进制语法
6
6
  * 支持 Display P3 广色域显示器
7
+ * 增强 math() 函数,支持复杂数学计算
7
8
  */
8
9
  (function(root, factory) {
9
10
  if (typeof define === 'function' && define.amd) {
@@ -26,9 +27,11 @@
26
27
  enableNesting: true,
27
28
  autoProcessStyleTags: true,
28
29
  styleTagAttribute: 'e',
29
- convertLabToRGB: true, // 是否将lab颜色转换为rgb
30
- convertLchToRGB: true, // 是否将lch颜色转换为rgb
31
- enableP3: true, // 是否启用P3广色域支持
30
+ convertLabToRGB: true,
31
+ convertLchToRGB: true,
32
+ enableP3: true,
33
+ enableMath: true, // 启用math()函数增强
34
+ mathPrecision: 6, // 数学计算精度
32
35
  };
33
36
 
34
37
  // 全局P3支持检测结果
@@ -181,6 +184,252 @@
181
184
  return -1;
182
185
  }
183
186
 
187
+ /**
188
+ * 增强的数学表达式解析和计算
189
+ * @param {string} expression - 数学表达式
190
+ * @param {Object} config - 配置对象
191
+ * @returns {string} 计算结果
192
+ */
193
+ function evaluateMathExpression(expression, config) {
194
+ // 如果禁用math()增强,则返回原始表达式
195
+ if (!config.enableMath) {
196
+ return `math(${expression})`;
197
+ }
198
+
199
+ try {
200
+ // 清理表达式:移除空白字符
201
+ let cleanExpr = expression.replace(/\s+/g, '');
202
+
203
+ // 解析数学表达式
204
+ const result = parseMathExpression(cleanExpr, config);
205
+
206
+ // 如果结果包含单位,保留原始格式
207
+ if (hasUnits(cleanExpr)) {
208
+ // 处理带单位的表达式
209
+ return processUnitExpression(cleanExpr, result);
210
+ } else {
211
+ // 纯数字表达式,直接计算结果
212
+ return roundNumber(result, config.mathPrecision);
213
+ }
214
+ } catch (error) {
215
+ console.warn('math()表达式解析失败:', expression, error);
216
+ return `math(${expression})`;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * 解析数学表达式
222
+ * @param {string} expr - 清理后的表达式
223
+ * @param {Object} config - 配置对象
224
+ * @returns {number} 计算结果
225
+ */
226
+ function parseMathExpression(expr, config) {
227
+ // 处理括号
228
+ while (expr.includes('(') && expr.includes(')')) {
229
+ const start = expr.lastIndexOf('(');
230
+ const end = expr.indexOf(')', start);
231
+
232
+ if (end === -1) break;
233
+
234
+ const inner = expr.substring(start + 1, end);
235
+ const innerResult = parseMathExpression(inner, config);
236
+
237
+ expr = expr.substring(0, start) + innerResult + expr.substring(end + 1);
238
+ }
239
+
240
+ // 现在expr应该没有括号了
241
+ return evaluateSimpleExpression(expr, config);
242
+ }
243
+
244
+ /**
245
+ * 评估简单表达式(无括号)
246
+ * @param {string} expr - 简单表达式
247
+ * @param {Object} config - 配置对象
248
+ * @returns {number} 计算结果
249
+ */
250
+ function evaluateSimpleExpression(expr, config) {
251
+ // 处理操作符优先级:乘除优先于加减
252
+ const operators = [
253
+ { regex: /([\d.]+(?:[a-zA-Z%]+)?)\*([\d.]+(?:[a-zA-Z%]+)?)/, handler: multiply },
254
+ { regex: /([\d.]+(?:[a-zA-Z%]+)?)\/([\d.]+(?:[a-zA-Z%]+)?)/, handler: divide },
255
+ { regex: /([\d.]+(?:[a-zA-Z%]+)?)\+([\d.]+(?:[a-zA-Z%]+)?)/, handler: add },
256
+ { regex: /([\d.]+(?:[a-zA-Z%]+)?)-([\d.]+(?:[a-zA-Z%]+)?)/, handler: subtract }
257
+ ];
258
+
259
+ let lastExpr = expr;
260
+
261
+ // 按照优先级处理操作符
262
+ for (const op of operators) {
263
+ let match;
264
+ while ((match = expr.match(op.regex)) !== null) {
265
+ const left = parseValueWithUnit(match[1]);
266
+ const right = parseValueWithUnit(match[2]);
267
+ const result = op.handler(left, right);
268
+
269
+ // 替换匹配的部分
270
+ expr = expr.substring(0, match.index) +
271
+ result.value +
272
+ expr.substring(match.index + match[0].length);
273
+ }
274
+ }
275
+
276
+ // 如果表达式无法进一步简化,尝试解析为数字
277
+ if (expr !== lastExpr) {
278
+ return evaluateSimpleExpression(expr, config);
279
+ }
280
+
281
+ // 解析最终结果
282
+ const parsed = parseValueWithUnit(expr);
283
+ return parsed.value;
284
+ }
285
+
286
+ /**
287
+ * 解析带单位的数值
288
+ * @param {string} str - 字符串值
289
+ * @returns {Object} {value: number, unit: string}
290
+ */
291
+ function parseValueWithUnit(str) {
292
+ // 匹配数值和单位
293
+ const match = str.match(/^([\d.]+)([a-zA-Z%]*)$/);
294
+
295
+ if (!match) {
296
+ throw new Error(`无法解析值: ${str}`);
297
+ }
298
+
299
+ const value = parseFloat(match[1]);
300
+ const unit = match[2] || '';
301
+
302
+ return { value, unit };
303
+ }
304
+
305
+ /**
306
+ * 检查表达式是否包含单位
307
+ * @param {string} expr - 表达式
308
+ * @returns {boolean} 是否包含单位
309
+ */
310
+ function hasUnits(expr) {
311
+ return /[a-zA-Z%]/.test(expr);
312
+ }
313
+
314
+ /**
315
+ * 处理带单位的表达式
316
+ * @param {string} originalExpr - 原始表达式
317
+ * @param {number} result - 计算结果
318
+ * @returns {string} 处理后的表达式
319
+ */
320
+ function processUnitExpression(originalExpr, result) {
321
+ // 提取原始表达式中的单位
322
+ const unitMatch = originalExpr.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);
323
+
324
+ if (unitMatch) {
325
+ return result + unitMatch[1];
326
+ }
327
+
328
+ // 如果没有明确单位,检查是否为百分比
329
+ if (originalExpr.includes('%')) {
330
+ return result + '%';
331
+ }
332
+
333
+ // 默认为像素
334
+ return result + 'px';
335
+ }
336
+
337
+ // 数学运算函数
338
+ function multiply(a, b) {
339
+ if (a.unit === b.unit || (!a.unit && !b.unit)) {
340
+ return { value: a.value * b.value, unit: a.unit || b.unit };
341
+ }
342
+
343
+ // 单位不匹配,返回原始表达式
344
+ return { value: `${a.value}${a.unit}*${b.value}${b.unit}`, unit: '' };
345
+ }
346
+
347
+ function divide(a, b) {
348
+ if (b.value === 0) {
349
+ throw new Error('除以零');
350
+ }
351
+
352
+ if (a.unit && !b.unit) {
353
+ // 如 100px / 2
354
+ return { value: a.value / b.value, unit: a.unit };
355
+ } else if (a.unit === b.unit) {
356
+ // 如 100px / 2px
357
+ return { value: a.value / b.value, unit: '' };
358
+ }
359
+
360
+ // 单位不匹配,返回原始表达式
361
+ return { value: `${a.value}${a.unit}/${b.value}${b.unit}`, unit: '' };
362
+ }
363
+
364
+ function add(a, b) {
365
+ if (a.unit === b.unit) {
366
+ return { value: a.value + b.value, unit: a.unit };
367
+ }
368
+
369
+ // 单位不匹配,返回原始表达式
370
+ return { value: `${a.value}${a.unit}+${b.value}${b.unit}`, unit: '' };
371
+ }
372
+
373
+ function subtract(a, b) {
374
+ if (a.unit === b.unit) {
375
+ return { value: a.value - b.value, unit: a.unit };
376
+ }
377
+
378
+ // 单位不匹配,返回原始表达式
379
+ return { value: `${a.value}${a.unit}-${b.value}${b.unit}`, unit: '' };
380
+ }
381
+
382
+ /**
383
+ * 四舍五入到指定精度
384
+ * @param {number} num - 要四舍五入的数字
385
+ * @param {number} precision - 精度
386
+ * @returns {string} 四舍五入后的数字字符串
387
+ */
388
+ function roundNumber(num, precision = 6) {
389
+ const factor = Math.pow(10, precision);
390
+ const rounded = Math.round(num * factor) / factor;
391
+
392
+ // 移除不必要的尾随零
393
+ const str = rounded.toString();
394
+ if (str.includes('.')) {
395
+ return str.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
396
+ }
397
+ return str;
398
+ }
399
+
400
+ /**
401
+ * 处理所有的math()函数
402
+ * @param {string} cssValue - CSS属性值
403
+ * @param {Object} config - 配置对象
404
+ * @returns {string} 转换后的CSS值
405
+ */
406
+ function processMathFunctions(cssValue, config) {
407
+ if (!config.enableMath) {
408
+ return cssValue;
409
+ }
410
+
411
+ let result = cssValue;
412
+
413
+ // 匹配math()函数
414
+ const mathRegex = /math\(([^)]+)\)/gi;
415
+
416
+ // 递归处理嵌套的math()函数
417
+ const processMath = (str) => {
418
+ return str.replace(mathRegex, (match, expression) => {
419
+ return evaluateMathExpression(expression, config);
420
+ });
421
+ };
422
+
423
+ // 递归处理,直到没有更多math()函数
424
+ let lastResult;
425
+ do {
426
+ lastResult = result;
427
+ result = processMath(result);
428
+ } while (result !== lastResult && result.includes('math('));
429
+
430
+ return result;
431
+ }
432
+
184
433
  /**
185
434
  * 解析并转换所有的 Lab 和 LCH 颜色(全局一次性处理)
186
435
  * @param {string} cssValue - CSS属性值
@@ -616,21 +865,32 @@
616
865
  }
617
866
 
618
867
  /**
619
- * 处理CSS值,根据配置转换LABLCH颜色
868
+ * 处理CSS值,根据配置转换LABLCH颜色和math()函数
620
869
  * @param {string} value - CSS属性值
621
870
  * @param {Object} config - 配置对象
622
871
  * @returns {string} 处理后的值
623
872
  */
624
873
  function processCSSValue(value, config) {
625
- // 直接调用全局转换函数
626
- return convertAllLabLchColors(value, config);
874
+ let result = value;
875
+
876
+ // 1. 首先处理math()函数
877
+ if (config.enableMath) {
878
+ result = processMathFunctions(result, config);
879
+ }
880
+
881
+ // 2. 然后处理LAB和LCH颜色
882
+ if (config.convertLabToRGB || config.convertLchToRGB) {
883
+ result = convertAllLabLchColors(result, config);
884
+ }
885
+
886
+ return result;
627
887
  }
628
888
 
629
889
  // 私有方法:提取变量定义并移除(支持嵌套作用域)
630
890
  function extractVariablesAndCSS(cssText, config) {
631
891
  const lines = cssText.split('\n');
632
892
  const globalVariables = {};
633
- const selectorVariables = new Map(); // 选择器 -> 变量映射
893
+ const selectorVariables = new Map();
634
894
  let cssWithoutVars = '';
635
895
  let currentSelector = null;
636
896
  let inSelectorBlock = false;
@@ -644,8 +904,8 @@
644
904
 
645
905
  if (varMatch) {
646
906
  const [, varName, varValue] = varMatch;
647
- // 处理变量值中的LAB/LCH颜色转换
648
- const processedValue = convertAllLabLchColors(
907
+ // 处理变量值中的数学表达式和颜色转换
908
+ const processedValue = processCSSValue(
649
909
  replaceVariableUsesInValue(varValue.trim(), {
650
910
  ...globalVariables,
651
911
  ...(currentSelector ? selectorVariables.get(currentSelector) || {} : {})
@@ -764,11 +1024,11 @@
764
1024
  if (!trimmed.includes('{') && !trimmed.includes('}') && trimmed.includes(':')) {
765
1025
  if (stack.length > 0) {
766
1026
  const currentRule = stack[stack.length - 1];
767
- // 处理属性值中的LAB/LCH颜色
1027
+ // 处理属性值中的math()函数和颜色转换
768
1028
  const parsed = parseSingleLineCSS(trimmed);
769
1029
  parsed.forEach(obj => {
770
1030
  const key = Object.keys(obj)[0];
771
- const value = convertAllLabLchColors(obj[key], config);
1031
+ const value = processCSSValue(obj[key], config);
772
1032
  currentRule.properties.push(`${key}: ${value}`);
773
1033
  });
774
1034
  }
@@ -821,11 +1081,11 @@
821
1081
  if (rule.properties.length > 0) {
822
1082
  result += (isAt ? "" : fullSelector) + ' {\n';
823
1083
  for (const prop of rule.properties) {
824
- // 再次处理属性值(确保LAB/LCH颜色被转换)
1084
+ // 再次处理属性值(确保math()函数和颜色被转换)
825
1085
  const parsed = parseSingleLineCSS(prop);
826
1086
  parsed.forEach(obj => {
827
1087
  const key = Object.keys(obj)[0];
828
- const value = convertAllLabLchColors(obj[key], config);
1088
+ const value = processCSSValue(obj[key], config);
829
1089
  result += (isParentAt ? ' ' : '') + ` ${key}: ${value};\n`;
830
1090
  });
831
1091
  }
@@ -870,10 +1130,8 @@
870
1130
  return `var(--${varName})`;
871
1131
  });
872
1132
 
873
- // 最后处理所有的LABLCH颜色(一次性全局处理)
874
- if (config.convertLabToRGB || config.convertLchToRGB) {
875
- result = convertAllLabLchColors(result, config);
876
- }
1133
+ // 最后处理所有的LABLCH颜色和math()函数
1134
+ result = processCSSValue(result, config);
877
1135
 
878
1136
  return result;
879
1137
  }
@@ -886,7 +1144,7 @@
886
1144
 
887
1145
  const declarations = Object.entries(variables)
888
1146
  .map(([name, value]) => {
889
- const processedValue = convertAllLabLchColors(
1147
+ const processedValue = processCSSValue(
890
1148
  replaceVariableUsesInValue(value, variables),
891
1149
  config
892
1150
  );
@@ -922,12 +1180,8 @@
922
1180
  currentSelector = null;
923
1181
  }
924
1182
 
925
- // 使用全局转换函数处理所有颜色
926
- if (config.convertLabToRGB || config.convertLchToRGB) {
927
- result += convertAllLabLchColors(line, config) + '\n';
928
- } else {
929
- result += line + '\n';
930
- }
1183
+ // 使用全局处理函数处理所有math()和颜色
1184
+ result += processCSSValue(line, config) + '\n';
931
1185
  }
932
1186
 
933
1187
  return result.trim();
@@ -1024,7 +1278,7 @@
1024
1278
  convert,
1025
1279
  apply,
1026
1280
  config,
1027
- version: '1.8.0',
1281
+ version: '1.9.0',
1028
1282
 
1029
1283
  // 检测P3支持
1030
1284
  supportsP3: detectP3Support(),
@@ -1032,6 +1286,58 @@
1032
1286
  // 重新检测P3支持(如果配置变化)
1033
1287
  detectP3Support: detectP3Support,
1034
1288
 
1289
+ // 数学计算工具方法
1290
+ math: {
1291
+ /**
1292
+ * 计算数学表达式
1293
+ * @param {string} expression - 数学表达式
1294
+ * @returns {string} 计算结果
1295
+ */
1296
+ evaluate: function(expression) {
1297
+ return evaluateMathExpression(expression, defaultConfig);
1298
+ },
1299
+
1300
+ /**
1301
+ * 解析带单位的数值
1302
+ * @param {string} value - 带单位的字符串
1303
+ * @returns {Object} {value: number, unit: string}
1304
+ */
1305
+ parseUnit: parseValueWithUnit,
1306
+
1307
+ /**
1308
+ * 四舍五入数字
1309
+ * @param {number} num - 要四舍五入的数字
1310
+ * @param {number} precision - 精度
1311
+ * @returns {string} 四舍五入后的数字字符串
1312
+ */
1313
+ round: roundNumber,
1314
+
1315
+ /**
1316
+ * 测试数学表达式
1317
+ * @param {string} expression - 要测试的表达式
1318
+ * @param {Object} testConfig - 测试配置
1319
+ * @returns {Object} 测试结果
1320
+ */
1321
+ test: function(expression, testConfig = {}) {
1322
+ const config = { ...defaultConfig, ...testConfig };
1323
+ try {
1324
+ const result = evaluateMathExpression(expression, config);
1325
+ return {
1326
+ success: true,
1327
+ expression,
1328
+ result,
1329
+ parsed: parseValueWithUnit(result.replace(/^calc\(|\)$/g, ''))
1330
+ };
1331
+ } catch (error) {
1332
+ return {
1333
+ success: false,
1334
+ expression,
1335
+ error: error.message
1336
+ };
1337
+ }
1338
+ }
1339
+ },
1340
+
1035
1341
  // 颜色转换工具方法
1036
1342
  colorUtils: {
1037
1343
  labToRGB: preciseLabToRGB,
@@ -1152,12 +1458,12 @@
1152
1458
  const element = this;
1153
1459
  return new Proxy({}, {
1154
1460
  get(target, prop) {
1155
- const varName = prop.startsWith('$') ? prop.slice(1) : prop;
1461
+ const varName = prop.startsWith('--') ? prop : `--${prop}`;
1156
1462
  return element.style.getPropertyValue(varName);
1157
1463
  },
1158
1464
 
1159
1465
  set(target, prop, value) {
1160
- const varName = prop.startsWith('$') ? prop.slice(1) : prop;
1466
+ const varName = prop.startsWith('--') ? prop : `--${prop}`;
1161
1467
  element.style.setProperty(varName, value);
1162
1468
  return true;
1163
1469
  }
package/styimat.min.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * 支持 Lab 和 LCH 颜色空间转换为 RGB(CSS标准格式)
5
5
  * 支持 lab# 和 lch# 十六进制语法
6
6
  * 支持 Display P3 广色域显示器
7
+ * 增强 math() 函数,支持复杂数学计算
7
8
  */
8
9
  (function(root,factory){if(typeof define==="function"&&define.amd){
9
10
  // AMD 支持 (RequireJS)
@@ -13,9 +14,8 @@ module.exports=factory()}else{
13
14
  // 浏览器全局变量
14
15
  root.styimat=factory()}})(typeof self!=="undefined"?self:this,function(){
15
16
  // 默认配置
16
- let defaultConfig={rootSelector:":root",variablePrefix:"--",preserveOriginal:false,indentSize:2,enableNesting:true,autoProcessStyleTags:true,styleTagAttribute:"e",convertLabToRGB:true,// 是否将lab颜色转换为rgb
17
- convertLchToRGB:true,// 是否将lch颜色转换为rgb
18
- enableP3:true};
17
+ let defaultConfig={rootSelector:":root",variablePrefix:"--",preserveOriginal:false,indentSize:2,enableNesting:true,autoProcessStyleTags:true,styleTagAttribute:"e",convertLabToRGB:true,convertLchToRGB:true,enableP3:true,enableMath:true,// 启用math()函数增强
18
+ mathPrecision:6};
19
19
  // 全局P3支持检测结果
20
20
  let p3Supported=null;
21
21
  /**
@@ -68,6 +68,108 @@ result.push({[property]:value})}return result}
68
68
  if((char==='"'||char==="'")&&!inQuotes){inQuotes=true;quoteChar=char}else if(char===quoteChar&&inQuotes){inQuotes=false;quoteChar=""}
69
69
  // 如果找到冒号且不在引号内,返回位置
70
70
  if(char===":"&&!inQuotes){return i}}return-1}
71
+ /**
72
+ * 增强的数学表达式解析和计算
73
+ * @param {string} expression - 数学表达式
74
+ * @param {Object} config - 配置对象
75
+ * @returns {string} 计算结果
76
+ */function evaluateMathExpression(expression,config){
77
+ // 如果禁用math()增强,则返回原始表达式
78
+ if(!config.enableMath){return`math(${expression})`}try{
79
+ // 清理表达式:移除空白字符
80
+ let cleanExpr=expression.replace(/\s+/g,"");
81
+ // 解析数学表达式
82
+ const result=parseMathExpression(cleanExpr,config);
83
+ // 如果结果包含单位,保留原始格式
84
+ if(hasUnits(cleanExpr)){
85
+ // 处理带单位的表达式
86
+ return processUnitExpression(cleanExpr,result)}else{
87
+ // 纯数字表达式,直接计算结果
88
+ return roundNumber(result,config.mathPrecision)}}catch(error){console.warn("math()表达式解析失败:",expression,error);return`math(${expression})`}}
89
+ /**
90
+ * 解析数学表达式
91
+ * @param {string} expr - 清理后的表达式
92
+ * @param {Object} config - 配置对象
93
+ * @returns {number} 计算结果
94
+ */function parseMathExpression(expr,config){
95
+ // 处理括号
96
+ while(expr.includes("(")&&expr.includes(")")){const start=expr.lastIndexOf("(");const end=expr.indexOf(")",start);if(end===-1)break;const inner=expr.substring(start+1,end);const innerResult=parseMathExpression(inner,config);expr=expr.substring(0,start)+innerResult+expr.substring(end+1)}
97
+ // 现在expr应该没有括号了
98
+ return evaluateSimpleExpression(expr,config)}
99
+ /**
100
+ * 评估简单表达式(无括号)
101
+ * @param {string} expr - 简单表达式
102
+ * @param {Object} config - 配置对象
103
+ * @returns {number} 计算结果
104
+ */function evaluateSimpleExpression(expr,config){
105
+ // 处理操作符优先级:乘除优先于加减
106
+ const operators=[{regex:/([\d.]+(?:[a-zA-Z%]+)?)\*([\d.]+(?:[a-zA-Z%]+)?)/,handler:multiply},{regex:/([\d.]+(?:[a-zA-Z%]+)?)\/([\d.]+(?:[a-zA-Z%]+)?)/,handler:divide},{regex:/([\d.]+(?:[a-zA-Z%]+)?)\+([\d.]+(?:[a-zA-Z%]+)?)/,handler:add},{regex:/([\d.]+(?:[a-zA-Z%]+)?)-([\d.]+(?:[a-zA-Z%]+)?)/,handler:subtract}];let lastExpr=expr;
107
+ // 按照优先级处理操作符
108
+ for(const op of operators){let match;while((match=expr.match(op.regex))!==null){const left=parseValueWithUnit(match[1]);const right=parseValueWithUnit(match[2]);const result=op.handler(left,right);
109
+ // 替换匹配的部分
110
+ expr=expr.substring(0,match.index)+result.value+expr.substring(match.index+match[0].length)}}
111
+ // 如果表达式无法进一步简化,尝试解析为数字
112
+ if(expr!==lastExpr){return evaluateSimpleExpression(expr,config)}
113
+ // 解析最终结果
114
+ const parsed=parseValueWithUnit(expr);return parsed.value}
115
+ /**
116
+ * 解析带单位的数值
117
+ * @param {string} str - 字符串值
118
+ * @returns {Object} {value: number, unit: string}
119
+ */function parseValueWithUnit(str){
120
+ // 匹配数值和单位
121
+ const match=str.match(/^([\d.]+)([a-zA-Z%]*)$/);if(!match){throw new Error(`无法解析值: ${str}`)}const value=parseFloat(match[1]);const unit=match[2]||"";return{value:value,unit:unit}}
122
+ /**
123
+ * 检查表达式是否包含单位
124
+ * @param {string} expr - 表达式
125
+ * @returns {boolean} 是否包含单位
126
+ */function hasUnits(expr){return/[a-zA-Z%]/.test(expr)}
127
+ /**
128
+ * 处理带单位的表达式
129
+ * @param {string} originalExpr - 原始表达式
130
+ * @param {number} result - 计算结果
131
+ * @returns {string} 处理后的表达式
132
+ */function processUnitExpression(originalExpr,result){
133
+ // 提取原始表达式中的单位
134
+ const unitMatch=originalExpr.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);if(unitMatch){return result+unitMatch[1]}
135
+ // 如果没有明确单位,检查是否为百分比
136
+ if(originalExpr.includes("%")){return result+"%"}
137
+ // 默认为像素
138
+ return result+"px"}
139
+ // 数学运算函数
140
+ function multiply(a,b){if(a.unit===b.unit||!a.unit&&!b.unit){return{value:a.value*b.value,unit:a.unit||b.unit}}
141
+ // 单位不匹配,返回原始表达式
142
+ return{value:`${a.value}${a.unit}*${b.value}${b.unit}`,unit:""}}function divide(a,b){if(b.value===0){throw new Error("除以零")}if(a.unit&&!b.unit){
143
+ // 如 100px / 2
144
+ return{value:a.value/b.value,unit:a.unit}}else if(a.unit===b.unit){
145
+ // 如 100px / 2px
146
+ return{value:a.value/b.value,unit:""}}
147
+ // 单位不匹配,返回原始表达式
148
+ return{value:`${a.value}${a.unit}/${b.value}${b.unit}`,unit:""}}function add(a,b){if(a.unit===b.unit){return{value:a.value+b.value,unit:a.unit}}
149
+ // 单位不匹配,返回原始表达式
150
+ return{value:`${a.value}${a.unit}+${b.value}${b.unit}`,unit:""}}function subtract(a,b){if(a.unit===b.unit){return{value:a.value-b.value,unit:a.unit}}
151
+ // 单位不匹配,返回原始表达式
152
+ return{value:`${a.value}${a.unit}-${b.value}${b.unit}`,unit:""}}
153
+ /**
154
+ * 四舍五入到指定精度
155
+ * @param {number} num - 要四舍五入的数字
156
+ * @param {number} precision - 精度
157
+ * @returns {string} 四舍五入后的数字字符串
158
+ */function roundNumber(num,precision=6){const factor=Math.pow(10,precision);const rounded=Math.round(num*factor)/factor;
159
+ // 移除不必要的尾随零
160
+ const str=rounded.toString();if(str.includes(".")){return str.replace(/(\.\d*?)0+$/,"$1").replace(/\.$/,"")}return str}
161
+ /**
162
+ * 处理所有的math()函数
163
+ * @param {string} cssValue - CSS属性值
164
+ * @param {Object} config - 配置对象
165
+ * @returns {string} 转换后的CSS值
166
+ */function processMathFunctions(cssValue,config){if(!config.enableMath){return cssValue}let result=cssValue;
167
+ // 匹配math()函数
168
+ const mathRegex=/math\(([^)]+)\)/gi;
169
+ // 递归处理嵌套的math()函数
170
+ const processMath=str=>str.replace(mathRegex,(match,expression)=>evaluateMathExpression(expression,config));
171
+ // 递归处理,直到没有更多math()函数
172
+ let lastResult;do{lastResult=result;result=processMath(result)}while(result!==lastResult&&result.includes("math("));return result}
71
173
  /**
72
174
  * 解析并转换所有的 Lab 和 LCH 颜色(全局一次性处理)
73
175
  * @param {string} cssValue - CSS属性值
@@ -218,20 +320,21 @@ const rgb=preciseLabToRGB(L,a,b);if(alpha!==null){return`rgba(${rgb.r}, ${rgb.g}
218
320
  // 使用P3
219
321
  const p3=labToP3(L,a,b);if(alpha!==null){return`color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)} / ${alpha})`}return`color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)})`}}
220
322
  /**
221
- * 处理CSS值,根据配置转换LABLCH颜色
323
+ * 处理CSS值,根据配置转换LABLCH颜色和math()函数
222
324
  * @param {string} value - CSS属性值
223
325
  * @param {Object} config - 配置对象
224
326
  * @returns {string} 处理后的值
225
- */function processCSSValue(value,config){
226
- // 直接调用全局转换函数
227
- return convertAllLabLchColors(value,config)}
327
+ */function processCSSValue(value,config){let result=value;
328
+ // 1. 首先处理math()函数
329
+ if(config.enableMath){result=processMathFunctions(result,config)}
330
+ // 2. 然后处理LAB和LCH颜色
331
+ if(config.convertLabToRGB||config.convertLchToRGB){result=convertAllLabLchColors(result,config)}return result}
228
332
  // 私有方法:提取变量定义并移除(支持嵌套作用域)
229
- function extractVariablesAndCSS(cssText,config){const lines=cssText.split("\n");const globalVariables={};const selectorVariables=new Map;// 选择器 -> 变量映射
230
- let cssWithoutVars="";let currentSelector=null;let inSelectorBlock=false;let currentIndent=0;for(let line of lines){const trimmed=line.trim();
333
+ function extractVariablesAndCSS(cssText,config){const lines=cssText.split("\n");const globalVariables={};const selectorVariables=new Map;let cssWithoutVars="";let currentSelector=null;let inSelectorBlock=false;let currentIndent=0;for(let line of lines){const trimmed=line.trim();
231
334
  // 检查是否是变量定义
232
335
  const varMatch=trimmed.match(/^\$([a-zA-Z0-9_-]+)\s*:\s*(.+?);?$/);if(varMatch){const[,varName,varValue]=varMatch;
233
- // 处理变量值中的LAB/LCH颜色转换
234
- const processedValue=convertAllLabLchColors(replaceVariableUsesInValue(varValue.trim(),{...globalVariables,...currentSelector?selectorVariables.get(currentSelector)||{}:{}}),config);if(currentSelector){
336
+ // 处理变量值中的数学表达式和颜色转换
337
+ const processedValue=processCSSValue(replaceVariableUsesInValue(varValue.trim(),{...globalVariables,...currentSelector?selectorVariables.get(currentSelector)||{}:{}}),config);if(currentSelector){
235
338
  // 选择器内部的变量
236
339
  if(!selectorVariables.has(currentSelector)){selectorVariables.set(currentSelector,{})}selectorVariables.get(currentSelector)[varName]=processedValue}else{
237
340
  // 全局变量(:root 中)
@@ -263,8 +366,8 @@ const rule={selector:selector,properties:[],children:[],indentLevel:indentLevel}
263
366
  stack.push(rule);continue}
264
367
  // 处理属性行(不包含 { 或 } 的行)
265
368
  if(!trimmed.includes("{")&&!trimmed.includes("}")&&trimmed.includes(":")){if(stack.length>0){const currentRule=stack[stack.length-1];
266
- // 处理属性值中的LAB/LCH颜色
267
- const parsed=parseSingleLineCSS(trimmed);parsed.forEach(obj=>{const key=Object.keys(obj)[0];const value=convertAllLabLchColors(obj[key],config);currentRule.properties.push(`${key}: ${value}`)})}continue}}
369
+ // 处理属性值中的math()函数和颜色转换
370
+ const parsed=parseSingleLineCSS(trimmed);parsed.forEach(obj=>{const key=Object.keys(obj)[0];const value=processCSSValue(obj[key],config);currentRule.properties.push(`${key}: ${value}`)})}continue}}
268
371
  // 处理栈中剩余规则
269
372
  while(stack.length>0){const rule=stack.pop();if(stack.length===0){rootRules.push(rule)}else{const parent=stack[stack.length-1];if(!parent.children)parent.children=[];parent.children.push(rule)}}
270
373
  // 将规则树转换为CSS字符串
@@ -277,8 +380,8 @@ const isAt=rule.selector.startsWith("@");let fullSelector=(isParentAt?" ":"")+r
277
380
  if(fullSelector.includes("&")){fullSelector=fullSelector.replace(/&/g,parentSelector)}else if(fullSelector.trim().startsWith(":")){fullSelector=parentSelector+fullSelector}else{fullSelector=parentSelector+(isParentAt?"":" ")+fullSelector}}
278
381
  // 如果有属性,生成规则块
279
382
  if(rule.properties.length>0){result+=(isAt?"":fullSelector)+" {\n";for(const prop of rule.properties){
280
- // 再次处理属性值(确保LAB/LCH颜色被转换)
281
- const parsed=parseSingleLineCSS(prop);parsed.forEach(obj=>{const key=Object.keys(obj)[0];const value=convertAllLabLchColors(obj[key],config);result+=(isParentAt?" ":"")+` ${key}: ${value};\n`})}result+=isParentAt?" }\n":"}\n\n"}
383
+ // 再次处理属性值(确保math()函数和颜色被转换)
384
+ const parsed=parseSingleLineCSS(prop);parsed.forEach(obj=>{const key=Object.keys(obj)[0];const value=processCSSValue(obj[key],config);result+=(isParentAt?" ":"")+` ${key}: ${value};\n`})}result+=isParentAt?" }\n":"}\n\n"}
282
385
  // 递归处理子规则
283
386
  if(rule.children&&rule.children.length>0){result+=convertRulesToCSS(rule.children,config,fullSelector)}if(isParentAt){result+="}\n\n"}}return result.trim()+(isParentAt?"\n\n":"")}
284
387
  // 替换变量值中的变量引用
@@ -289,16 +392,16 @@ function replaceVariableUses(cssText,globalVariables,selectorVariables,config){l
289
392
  for(const[varName,varValue]of Object.entries(globalVariables)){const varRegex=new RegExp(`\\$${varName}(?![a-zA-Z0-9_-])`,"g");result=result.replace(varRegex,`var(--${varName})`)}
290
393
  // 然后处理选择器特定的变量
291
394
  result=result.replace(/\$([a-zA-Z0-9_-]+)/g,(match,varName)=>`var(--${varName})`);
292
- // 最后处理所有的LABLCH颜色(一次性全局处理)
293
- if(config.convertLabToRGB||config.convertLchToRGB){result=convertAllLabLchColors(result,config)}return result}
395
+ // 最后处理所有的LABLCH颜色和math()函数
396
+ result=processCSSValue(result,config);return result}
294
397
  // 生成根规则
295
- function generateRootRule(variables,config){if(Object.keys(variables).length===0){return""}const declarations=Object.entries(variables).map(([name,value])=>{const processedValue=convertAllLabLchColors(replaceVariableUsesInValue(value,variables),config);return` --${name}: ${processedValue};`}).join("\n");return`${config.rootSelector} {\n${declarations}\n}\n\n`}
398
+ function generateRootRule(variables,config){if(Object.keys(variables).length===0){return""}const declarations=Object.entries(variables).map(([name,value])=>{const processedValue=processCSSValue(replaceVariableUsesInValue(value,variables),config);return` --${name}: ${processedValue};`}).join("\n");return`${config.rootSelector} {\n${declarations}\n}\n\n`}
296
399
  // 处理选择器内部的变量
297
400
  function injectSelectorVariables(cssText,selectorVariables,config){let result="";const lines=cssText.split("\n");let currentSelector=null;for(let line of lines){const trimmed=line.trim();if(trimmed.endsWith("{")){currentSelector=trimmed.slice(0,-1).trim()}if(trimmed==="}"&&currentSelector){
298
401
  // 在选择器结束前插入变量声明
299
402
  if(selectorVariables.has(currentSelector)){const vars=selectorVariables.get(currentSelector);const indent=line.match(/^(\s*)/)[0];for(const[varName,varValue]of Object.entries(vars)){result+=indent+" --"+varName+": "+varValue+";\n"}}currentSelector=null}
300
- // 使用全局转换函数处理所有颜色
301
- if(config.convertLabToRGB||config.convertLchToRGB){result+=convertAllLabLchColors(line,config)+"\n"}else{result+=line+"\n"}}return result.trim()}
403
+ // 使用全局处理函数处理所有math()和颜色
404
+ result+=processCSSValue(line,config)+"\n"}return result.trim()}
302
405
  // 主转换函数
303
406
  function convert(cssText,customConfig={}){const config={...defaultConfig,...customConfig};
304
407
  // 1. 提取变量定义(区分全局和选择器局部)
@@ -324,11 +427,39 @@ if(!config.preserveOriginal){styleTag.remove()}else{styleTag.style.display="none
324
427
  // 初始化自动处理
325
428
  function apply(cssText,customConfig={}){const config={...defaultConfig,...customConfig};if(cssText){const converted=convert(cssText,config);const styleEl=document.createElement("style");styleEl.textContent=converted;document.head.appendChild(styleEl);return styleEl}else{if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",()=>{autoProcessStyleTags(config)})}else{autoProcessStyleTags(config)}}}
326
429
  // 返回公共 API
327
- const api={convert:convert,apply:apply,config:config,version:"1.8.0",
430
+ const api={convert:convert,apply:apply,config:config,version:"1.9.0",
328
431
  // 检测P3支持
329
432
  supportsP3:detectP3Support(),
330
433
  // 重新检测P3支持(如果配置变化)
331
434
  detectP3Support:detectP3Support,
435
+ // 数学计算工具方法
436
+ math:{
437
+ /**
438
+ * 计算数学表达式
439
+ * @param {string} expression - 数学表达式
440
+ * @returns {string} 计算结果
441
+ */
442
+ evaluate:function(expression){return evaluateMathExpression(expression,defaultConfig)},
443
+ /**
444
+ * 解析带单位的数值
445
+ * @param {string} value - 带单位的字符串
446
+ * @returns {Object} {value: number, unit: string}
447
+ */
448
+ parseUnit:parseValueWithUnit,
449
+ /**
450
+ * 四舍五入数字
451
+ * @param {number} num - 要四舍五入的数字
452
+ * @param {number} precision - 精度
453
+ * @returns {string} 四舍五入后的数字字符串
454
+ */
455
+ round:roundNumber,
456
+ /**
457
+ * 测试数学表达式
458
+ * @param {string} expression - 要测试的表达式
459
+ * @param {Object} testConfig - 测试配置
460
+ * @returns {Object} 测试结果
461
+ */
462
+ test:function(expression,testConfig={}){const config={...defaultConfig,...testConfig};try{const result=evaluateMathExpression(expression,config);return{success:true,expression:expression,result:result,parsed:parseValueWithUnit(result.replace(/^calc\(|\)$/g,""))}}catch(error){return{success:false,expression:expression,error:error.message}}}},
332
463
  // 颜色转换工具方法
333
464
  colorUtils:{labToRGB:preciseLabToRGB,lchToLab:lchToLab,lchToRGB:function(L,C,H){const lab=lchToLab(L,C,H);return preciseLabToRGB(lab.L,lab.a,lab.b)},labToP3:labToP3,lchToP3:function(L,C,H){const lab=lchToLab(L,C,H);return labToP3(lab.L,lab.a,lab.b)},parseHexLab:function(hexString){const match=hexString.match(/lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i);if(!match)return null;const L_hex=match[1];const A_hex=match[2];const B_hex=match[3];const L=parseInt(L_hex,16)/255*100;const A=(parseInt(A_hex,16)-128)*1.5;const B=(parseInt(B_hex,16)-128)*1.5;return preciseLabToRGB(L,A,B)},parseHexLch:function(hexString){const match=hexString.match(/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/i);if(!match)return null;const L_hex=match[1];const C_hex=match[2];const H_dec=match[3];const L=parseInt(L_hex,16)/255*100;const C=parseInt(C_hex,16)/255*150;const H=parseInt(H_dec)/100*360;const lab=lchToLab(L,C,H);return preciseLabToRGB(lab.L,lab.a,lab.b)},
334
465
  /**
@@ -352,4 +483,4 @@ const labMatch=colorString.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?
352
483
  // 尝试解析lch()函数格式
353
484
  const lchMatch=colorString.match(/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i);if(lchMatch){let L=parseFloat(lchMatch[1]);const C=parseFloat(lchMatch[3]);let H=parseFloat(lchMatch[4]);const alpha=lchMatch[6]?lchMatch[6].includes("%")?parseFloat(lchMatch[6])/100:parseFloat(lchMatch[6]):null;const lab=lchToLab(L,C,H);const colorStr=generateColorString(lab.L,lab.a,lab.b,defaultConfig,alpha);return{L:L,C:C,H:H,alpha:alpha,lab:lab,rgb:preciseLabToRGB(lab.L,lab.a,lab.b),p3:labToP3(lab.L,lab.a,lab.b),colorString:colorStr}}return null}catch(error){console.warn("无法解析颜色:",colorString,error);return null}}}};
354
485
  // 自动初始化
355
- if(typeof window!=="undefined"){apply();Object.defineProperty(window.HTMLElement.prototype,"cssVar",{get(){const element=this;return new Proxy({},{get(target,prop){const varName=prop.startsWith("$")?prop.slice(1):prop;return element.style.getPropertyValue(varName)},set(target,prop,value){const varName=prop.startsWith("$")?prop.slice(1):prop;element.style.setProperty(varName,value);return true}})}})}return api});
486
+ if(typeof window!=="undefined"){apply();Object.defineProperty(window.HTMLElement.prototype,"cssVar",{get(){const element=this;return new Proxy({},{get(target,prop){const varName=prop.startsWith("--")?prop:`--${prop}`;return element.style.getPropertyValue(varName)},set(target,prop,value){const varName=prop.startsWith("--")?prop:`--${prop}`;element.style.setProperty(varName,value);return true}})}})}return api});