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 +29 -1
- package/package.json +1 -1
- package/styimat.js +79 -14
- package/styimat.min.js +33 -10
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;
|
|
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
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:
|
|
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 +=
|
|
1026
|
+
currentIndent += config.indentSize;
|
|
962
1027
|
}
|
|
963
1028
|
if (line.includes('}')) {
|
|
964
|
-
currentIndent = Math.max(0, currentIndent -
|
|
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 ?
|
|
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 ?
|
|
1156
|
+
result += (isParentAt ? " ".repeat(config.indentSize) : '') + " ".repeat(config.indentSize) +`${key}: ${value};\n`;
|
|
1094
1157
|
});
|
|
1095
1158
|
}
|
|
1096
|
-
result += isParentAt ? '
|
|
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() +
|
|
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 +=
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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+=
|
|
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;
|
|
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:[]
|
|
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?"
|
|
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?"
|
|
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()+
|
|
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==="}"&¤tSelector){
|
|
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+=
|
|
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={}){
|
|
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(
|
|
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. 生成根规则(全局变量)
|