styimat 1.7.0 → 1.8.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/README.md CHANGED
@@ -272,7 +272,35 @@ $font-family: 'Inter', sans-serif;
272
272
  margin: $local-var;
273
273
 
274
274
  &::before {
275
- content: $$ctx; //获取元素属性ctx
275
+ content: $$ctx; // 获取元素属性ctx
276
+ }
277
+ }
278
+ ```
279
+
280
+ ### 配置头语法
281
+
282
+ 您可以在CSS文件的开头使用配置头来设置预处理选项,配置行以 `#` 开头:
283
+
284
+ ```css
285
+ #indentSize 4
286
+ #autoProcessStyleTags true
287
+ #preserveOriginal true
288
+ #enableP3 false
289
+ #rootSelector :root
290
+ #enableNesting true
291
+ #enableMath true
292
+ #mathPrecision 8
293
+
294
+ /* 然后写您的CSS代码 */
295
+ $primary-color: lab#32a852;
296
+ $spacing: math(16px * 2 + 4px);
297
+
298
+ body {
299
+ color: $primary-color;
300
+ padding: $spacing;
301
+
302
+ .container {
303
+ margin: math($spacing / 2);
276
304
  }
277
305
  }
278
306
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "styimat",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "一个高效的CSS变量预处理库,支持Lab/LCH颜色空间自动转换、嵌套选择器和Display P3广色域,让现代CSS开发更简洁强大。",
5
5
  "keywords": [
6
6
  "css",
package/styimat.js CHANGED
@@ -9,6 +9,7 @@
9
9
  * 支持 lab# 和 lch# 十六进制语法
10
10
  * 支持 Display P3 广色域显示器
11
11
  * 增强 math() 函数,支持复杂数学计算
12
+ * 支持配置头
12
13
  */
13
14
  (function(root, factory) {
14
15
  if (typeof define === 'function' && define.amd) {
@@ -27,7 +28,7 @@
27
28
  rootSelector: ':root',
28
29
  variablePrefix: '--',
29
30
  preserveOriginal: false,
30
- indentSize: 2,
31
+ indentSize: 4,
31
32
  enableNesting: true,
32
33
  autoProcessStyleTags: true,
33
34
  styleTagAttribute: 'e',
@@ -41,6 +42,70 @@
41
42
  // 全局P3支持检测结果
42
43
  let p3Supported = null;
43
44
 
45
+ /**
46
+ * 解析配置头
47
+ * @param {string} cssText - CSS文本
48
+ * @returns {Object} {config: 配置对象, css: 清理后的CSS}
49
+ */
50
+ function parseConfigHeader(cssText) {
51
+ const config = { ...defaultConfig };
52
+ const lines = cssText.split('\n');
53
+ const cleanLines = [];
54
+
55
+ for (let line of lines) {
56
+ const trimmed = line.trim();
57
+
58
+ // 检查是否是配置行(以 # 开头)
59
+ if (trimmed.startsWith('#')) {
60
+ const configLine = trimmed.substring(1).trim();
61
+ const firstSpace = configLine.indexOf(' ');
62
+
63
+ if (firstSpace !== -1) {
64
+ const key = configLine.substring(0, firstSpace).trim();
65
+ const value = configLine.substring(firstSpace + 1).trim();
66
+
67
+ // 转换配置值
68
+ const configKey = camelCase(key);
69
+
70
+ if (configKey in config) {
71
+ // 根据值的类型进行转换
72
+ if (value === 'true' || value === 'false') {
73
+ config[configKey] = value === 'true';
74
+ } else if (!isNaN(value) && value.trim() !== '') {
75
+ // 数字类型
76
+ config[configKey] = Number(value);
77
+ } else {
78
+ // 字符串类型
79
+ config[configKey] = value;
80
+ }
81
+ } else {
82
+ console.warn(`未知的配置项: ${key}`);
83
+ }
84
+ } else {
85
+ console.warn(`无效的配置行: ${trimmed}`);
86
+ }
87
+ } else {
88
+ cleanLines.push(line);
89
+ }
90
+ }
91
+
92
+ return {
93
+ config,
94
+ css: cleanLines.join('\n')
95
+ };
96
+ }
97
+
98
+ /**
99
+ * 将连字符分隔的字符串转换为驼峰命名
100
+ * @param {string} str - 输入字符串
101
+ * @returns {string} 驼峰命名字符串
102
+ */
103
+ function camelCase(str) {
104
+ return str.replace(/-([a-z])/g, function(match, letter) {
105
+ return letter.toUpperCase();
106
+ });
107
+ }
108
+
44
109
  /**
45
110
  * 检测浏览器是否支持 Display P3
46
111
  * @returns {boolean} 是否支持P3
@@ -958,10 +1023,10 @@
958
1023
 
959
1024
  // 更新缩进级别
960
1025
  if (line.includes('{')) {
961
- currentIndent += 2;
1026
+ currentIndent += config.indentSize;
962
1027
  }
963
1028
  if (line.includes('}')) {
964
- currentIndent = Math.max(0, currentIndent - 2);
1029
+ currentIndent = Math.max(0, currentIndent - config.indentSize);
965
1030
  }
966
1031
  }
967
1032
 
@@ -987,7 +1052,6 @@
987
1052
 
988
1053
  // 计算缩进级别(假设使用空格缩进)
989
1054
  const indent = line.match(/^(\s*)/)[0].length;
990
- const indentLevel = Math.floor(indent / config.indentSize);
991
1055
 
992
1056
  // 处理规则块结束
993
1057
  if (trimmed === '}') {
@@ -1015,8 +1079,7 @@
1015
1079
  const rule = {
1016
1080
  selector: selector,
1017
1081
  properties: [],
1018
- children: [],
1019
- indentLevel: indentLevel
1082
+ children: []
1020
1083
  };
1021
1084
 
1022
1085
  // 推入堆栈
@@ -1064,7 +1127,7 @@
1064
1127
  for (const rule of rules) {
1065
1128
  // 构建完整选择器
1066
1129
  const isAt = rule.selector.startsWith("@");
1067
- let fullSelector = (isParentAt ? ' ' : '') + rule.selector;
1130
+ let fullSelector = (isParentAt ? " ".repeat(config.indentSize) : '') + rule.selector;
1068
1131
 
1069
1132
  if (isAt) {
1070
1133
  fullSelector = rule.selector + " {\n";
@@ -1090,10 +1153,10 @@
1090
1153
  parsed.forEach(obj => {
1091
1154
  const key = Object.keys(obj)[0];
1092
1155
  const value = processCSSValue(obj[key], config);
1093
- result += (isParentAt ? ' ' : '') + ` ${key}: ${value};\n`;
1156
+ result += (isParentAt ? " ".repeat(config.indentSize) : '') + " ".repeat(config.indentSize) +`${key}: ${value};\n`;
1094
1157
  });
1095
1158
  }
1096
- result += isParentAt ? ' }\n' : '}\n\n';
1159
+ result += isParentAt ? " ".repeat(config.indentSize) + '}\n' : '}\n\n';
1097
1160
  }
1098
1161
 
1099
1162
  // 递归处理子规则
@@ -1106,7 +1169,7 @@
1106
1169
  }
1107
1170
  }
1108
1171
 
1109
- return result.trim() + (isParentAt ? "\n\n" : "");
1172
+ return result.trim() + "\n\n";
1110
1173
  }
1111
1174
 
1112
1175
  // 替换变量值中的变量引用
@@ -1172,7 +1235,7 @@
1172
1235
  const vars = selectorVariables.get(currentSelector);
1173
1236
  const indent = line.match(/^(\s*)/)[0];
1174
1237
  for (const [varName, varValue] of Object.entries(vars)) {
1175
- result += indent + ' --' + varName + ': ' + varValue + ';\n';
1238
+ result += ' --' + varName + ': ' + varValue + ';\n';
1176
1239
  }
1177
1240
  }
1178
1241
  currentSelector = null;
@@ -1187,11 +1250,13 @@
1187
1250
 
1188
1251
  // 主转换函数
1189
1252
  function convert(cssText, customConfig = {}) {
1190
- const config = { ...defaultConfig, ...customConfig };
1253
+ // 先解析配置头
1254
+ const { config: headerConfig, css: cleanedCSS } = parseConfigHeader(cssText);
1255
+ const config = { ...defaultConfig, ...customConfig,...headerConfig };
1191
1256
 
1192
1257
  // 1. 提取变量定义(区分全局和选择器局部)
1193
1258
  const { globalVariables, selectorVariables, cssWithoutVars } =
1194
- extractVariablesAndCSS(cssText, config);
1259
+ extractVariablesAndCSS(cleanedCSS, config);
1195
1260
 
1196
1261
  // 2. 解析嵌套规则(如果启用)
1197
1262
  let processedCSS = cssWithoutVars.trim();
@@ -1505,7 +1570,7 @@
1505
1570
 
1506
1571
  // 将API的所有方法复制到主函数上
1507
1572
  Object.assign(styimat, api);
1508
- Object.setPrototypeOf(styimat, Function.prototype);
1573
+ Object.setPrototypeOf(styimat, Function.prototype);
1509
1574
 
1510
1575
  // 自动初始化
1511
1576
  if (typeof window !== 'undefined') {
package/styimat.min.js CHANGED
@@ -9,6 +9,7 @@
9
9
  * 支持 lab# 和 lch# 十六进制语法
10
10
  * 支持 Display P3 广色域显示器
11
11
  * 增强 math() 函数,支持复杂数学计算
12
+ * 支持配置头
12
13
  */
13
14
  (function(root,factory){if(typeof define==="function"&&define.amd){
14
15
  // AMD 支持 (RequireJS)
@@ -18,10 +19,30 @@ module.exports=factory()}else{
18
19
  // 浏览器全局变量
19
20
  root.styimat=factory()}})(typeof self!=="undefined"?self:this,function(){
20
21
  // 默认配置
21
- let defaultConfig={rootSelector:":root",variablePrefix:"--",preserveOriginal:false,indentSize:2,enableNesting:true,autoProcessStyleTags:true,styleTagAttribute:"e",convertLabToRGB:true,convertLchToRGB:true,enableP3:true,enableMath:true,// 启用math()函数增强
22
+ let defaultConfig={rootSelector:":root",variablePrefix:"--",preserveOriginal:false,indentSize:4,enableNesting:true,autoProcessStyleTags:true,styleTagAttribute:"e",convertLabToRGB:true,convertLchToRGB:true,enableP3:true,enableMath:true,// 启用math()函数增强
22
23
  mathPrecision:6};
23
24
  // 全局P3支持检测结果
24
25
  let p3Supported=null;
26
+ /**
27
+ * 解析配置头
28
+ * @param {string} cssText - CSS文本
29
+ * @returns {Object} {config: 配置对象, css: 清理后的CSS}
30
+ */function parseConfigHeader(cssText){const config={...defaultConfig};const lines=cssText.split("\n");const cleanLines=[];for(let line of lines){const trimmed=line.trim();
31
+ // 检查是否是配置行(以 # 开头)
32
+ if(trimmed.startsWith("#")){const configLine=trimmed.substring(1).trim();const firstSpace=configLine.indexOf(" ");if(firstSpace!==-1){const key=configLine.substring(0,firstSpace).trim();const value=configLine.substring(firstSpace+1).trim();
33
+ // 转换配置值
34
+ const configKey=camelCase(key);if(configKey in config){
35
+ // 根据值的类型进行转换
36
+ if(value==="true"||value==="false"){config[configKey]=value==="true"}else if(!isNaN(value)&&value.trim()!==""){
37
+ // 数字类型
38
+ config[configKey]=Number(value)}else{
39
+ // 字符串类型
40
+ config[configKey]=value}}else{console.warn(`未知的配置项: ${key}`)}}else{console.warn(`无效的配置行: ${trimmed}`)}}else{cleanLines.push(line)}}return{config:config,css:cleanLines.join("\n")}}
41
+ /**
42
+ * 将连字符分隔的字符串转换为驼峰命名
43
+ * @param {string} str - 输入字符串
44
+ * @returns {string} 驼峰命名字符串
45
+ */function camelCase(str){return str.replace(/-([a-z])/g,function(match,letter){return letter.toUpperCase()})}
25
46
  /**
26
47
  * 检测浏览器是否支持 Display P3
27
48
  * @returns {boolean} 是否支持P3
@@ -350,12 +371,12 @@ if(trimmed==="}"){if(inSelectorBlock){
350
371
  // 在选择器结束前插入变量声明
351
372
  if(currentSelector&&selectorVariables.has(currentSelector)){const vars=selectorVariables.get(currentSelector);const indent=" ".repeat(currentIndent);for(const[varName,varValue]of Object.entries(vars)){cssWithoutVars+=`${indent} --${varName}: ${varValue};\n`}}}inSelectorBlock=false;currentSelector=null}cssWithoutVars+=line+"\n";
352
373
  // 更新缩进级别
353
- if(line.includes("{")){currentIndent+=2}if(line.includes("}")){currentIndent=Math.max(0,currentIndent-2)}}return{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars.trim()}}
374
+ if(line.includes("{")){currentIndent+=config.indentSize}if(line.includes("}")){currentIndent=Math.max(0,currentIndent-config.indentSize)}}return{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars.trim()}}
354
375
  // 修复的嵌套解析函数
355
376
  function parseNestedRules(cssText,config){const lines=cssText.split("\n");const stack=[];// 存储规则信息:{selector, properties, children}
356
377
  const rootRules=[];let currentIndent=0;for(let i=0;i<lines.length;i++){const line=lines[i];const trimmed=line.trim();if(!trimmed)continue;
357
378
  // 计算缩进级别(假设使用空格缩进)
358
- const indent=line.match(/^(\s*)/)[0].length;const indentLevel=Math.floor(indent/config.indentSize);
379
+ const indent=line.match(/^(\s*)/)[0].length;
359
380
  // 处理规则块结束
360
381
  if(trimmed==="}"){if(stack.length>0){const rule=stack.pop();
361
382
  // 如果这个规则有父规则,添加到父规则的children中
@@ -365,7 +386,7 @@ rootRules.push(rule)}}continue}
365
386
  // 处理规则开始
366
387
  if(trimmed.endsWith("{")){const selector=trimmed.slice(0,-1).trim();
367
388
  // 创建新规则
368
- const rule={selector:selector,properties:[],children:[],indentLevel:indentLevel};
389
+ const rule={selector:selector,properties:[],children:[]};
369
390
  // 推入堆栈
370
391
  stack.push(rule);continue}
371
392
  // 处理属性行(不包含 { 或 } 的行)
@@ -379,15 +400,15 @@ return convertRulesToCSS(rootRules,config)}
379
400
  // 将规则树转换为CSS字符串
380
401
  function convertRulesToCSS(rules,config,parentSelector=""){let result="";const isParentAt=parentSelector.startsWith("@");for(const rule of rules){
381
402
  // 构建完整选择器
382
- const isAt=rule.selector.startsWith("@");let fullSelector=(isParentAt?" ":"")+rule.selector;if(isAt){fullSelector=rule.selector+" {\n"}if(parentSelector){
403
+ const isAt=rule.selector.startsWith("@");let fullSelector=(isParentAt?" ".repeat(config.indentSize):"")+rule.selector;if(isAt){fullSelector=rule.selector+" {\n"}if(parentSelector){
383
404
  // 处理 & 选择器
384
405
  if(fullSelector.includes("&")){fullSelector=fullSelector.replace(/&/g,parentSelector)}else if(fullSelector.trim().startsWith(":")){fullSelector=parentSelector+fullSelector}else{fullSelector=parentSelector+(isParentAt?"":" ")+fullSelector}}
385
406
  // 如果有属性,生成规则块
386
407
  if(rule.properties.length>0){result+=(isAt?"":fullSelector)+" {\n";for(const prop of rule.properties){
387
408
  // 再次处理属性值(确保math()函数和颜色被转换)
388
- 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"}
409
+ const parsed=parseSingleLineCSS(prop);parsed.forEach(obj=>{const key=Object.keys(obj)[0];const value=processCSSValue(obj[key],config);result+=(isParentAt?" ".repeat(config.indentSize):"")+" ".repeat(config.indentSize)+`${key}: ${value};\n`})}result+=isParentAt?" ".repeat(config.indentSize)+"}\n":"}\n\n"}
389
410
  // 递归处理子规则
390
- if(rule.children&&rule.children.length>0){result+=convertRulesToCSS(rule.children,config,fullSelector)}if(isParentAt){result+="}\n\n"}}return result.trim()+(isParentAt?"\n\n":"")}
411
+ if(rule.children&&rule.children.length>0){result+=convertRulesToCSS(rule.children,config,fullSelector)}if(isParentAt){result+="}\n\n"}}return result.trim()+"\n\n"}
391
412
  // 替换变量值中的变量引用
392
413
  function replaceVariableUsesInValue(value,variables){return value.replace(/\$([a-zA-Z0-9_-]+)/g,(match,varName)=>{if(variables[varName]){return`var(--${varName})`}return match})}
393
414
  // 替换变量使用
@@ -401,13 +422,15 @@ function generateRootRule(variables,config){if(Object.keys(variables).length===0
401
422
  // 处理选择器内部的变量
402
423
  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){
403
424
  // 在选择器结束前插入变量声明
404
- 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}
425
+ if(selectorVariables.has(currentSelector)){const vars=selectorVariables.get(currentSelector);const indent=line.match(/^(\s*)/)[0];for(const[varName,varValue]of Object.entries(vars)){result+=" --"+varName+": "+varValue+";\n"}}currentSelector=null}
405
426
  // 使用全局处理函数处理所有math()和颜色
406
427
  result+=processCSSValue(line,config)+"\n"}return result.trim()}
407
428
  // 主转换函数
408
- function convert(cssText,customConfig={}){const config={...defaultConfig,...customConfig};
429
+ function convert(cssText,customConfig={}){
430
+ // 先解析配置头
431
+ const{config:headerConfig,css:cleanedCSS}=parseConfigHeader(cssText);const config={...defaultConfig,...customConfig,...headerConfig};
409
432
  // 1. 提取变量定义(区分全局和选择器局部)
410
- const{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars}=extractVariablesAndCSS(cssText,config);
433
+ const{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars}=extractVariablesAndCSS(cleanedCSS,config);
411
434
  // 2. 解析嵌套规则(如果启用)
412
435
  let processedCSS=cssWithoutVars.trim();if(config.enableNesting&&cssWithoutVars.includes("{")){try{processedCSS=parseNestedRules(cssWithoutVars,config)}catch(error){console.warn("嵌套解析失败,使用原始CSS:",error)}}
413
436
  // 3. 生成根规则(全局变量)