styimat 1.11.0 → 1.11.2
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 +5 -6
- package/convert-styimat.js +46 -17
- package/package.json +6 -4
- package/styimat.min.js +26 -631
package/README.md
CHANGED
|
@@ -124,12 +124,10 @@ npx styimat
|
|
|
124
124
|
# 全局安装后使用
|
|
125
125
|
npm install -g styimat
|
|
126
126
|
styimat input.css output.css
|
|
127
|
-
|
|
128
|
-
# 监听
|
|
129
|
-
npx styimat --watch input.css output.css
|
|
130
|
-
npx styimat -w input.css output.css
|
|
131
127
|
```
|
|
132
128
|
|
|
129
|
+
**更多使用方法请 `npx styimat -h`**
|
|
130
|
+
|
|
133
131
|
## 高级用法
|
|
134
132
|
|
|
135
133
|
### 1. 作为函数直接调用
|
|
@@ -632,8 +630,9 @@ $primary-transparent: lab(55 190 0 / 0.8);
|
|
|
632
630
|
欢迎贡献代码!请遵循以下步骤:
|
|
633
631
|
|
|
634
632
|
1. **Fork 本仓库**
|
|
635
|
-
[](https://gitee.com/wxy6987/styimat/members)
|
|
634
|
+
|
|
635
|
+
2. **切换到特性分支**
|
|
637
636
|
```bash
|
|
638
637
|
git checkout -b feature/AmazingFeature
|
|
639
638
|
```
|
package/convert-styimat.js
CHANGED
|
@@ -18,6 +18,8 @@ async function main() {
|
|
|
18
18
|
|
|
19
19
|
if (arg === '--watch' || arg === '-w') {
|
|
20
20
|
watchMode = true;
|
|
21
|
+
} else if (arg === '--interactive' || arg === '-i') {
|
|
22
|
+
interactiveMode();
|
|
21
23
|
} else if (arg === '--help' || arg === '-h') {
|
|
22
24
|
showHelp();
|
|
23
25
|
process.exit(0);
|
|
@@ -38,17 +40,19 @@ async function main() {
|
|
|
38
40
|
|
|
39
41
|
function showHelp() {
|
|
40
42
|
console.log(`
|
|
41
|
-
用法:
|
|
43
|
+
用法: styimat [选项] [输入文件] [输出文件]
|
|
42
44
|
|
|
43
45
|
选项:
|
|
44
|
-
-w, --watch
|
|
45
|
-
-h, --help
|
|
46
|
+
-w, --watch 监控输入文件变化并自动转换
|
|
47
|
+
-h, --help 显示帮助信息
|
|
48
|
+
-i, --interactive 进入交互模式
|
|
46
49
|
|
|
47
50
|
示例:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
styimat # 从 stdin 读取,输出到 stdout
|
|
52
|
+
styimat input.css # 从文件读取,输出到 stdout
|
|
53
|
+
styimat input.css output.css # 从文件读取,输出到文件
|
|
54
|
+
styimat -w input.css output.css # 监控并自动转换
|
|
55
|
+
styimat -i # 进入交互模式
|
|
52
56
|
`);
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -104,7 +108,7 @@ async function main() {
|
|
|
104
108
|
|
|
105
109
|
// 处理进程终止
|
|
106
110
|
process.on('SIGINT', () => {
|
|
107
|
-
console.log('
|
|
111
|
+
console.log('停止监控...\n');
|
|
108
112
|
watcher.close();
|
|
109
113
|
process.exit(0);
|
|
110
114
|
});
|
|
@@ -119,7 +123,7 @@ async function main() {
|
|
|
119
123
|
async function convertFile(inputFilePath, outputFilePath) {
|
|
120
124
|
try {
|
|
121
125
|
const inputContent = fs.readFileSync(inputFilePath, 'utf8');
|
|
122
|
-
const outputContent = await
|
|
126
|
+
const outputContent = await convertStyimat(inputContent);
|
|
123
127
|
fs.writeFileSync(outputFilePath, outputContent, 'utf8');
|
|
124
128
|
console.log(`${new Date().toLocaleTimeString()} - 转换完成: ${inputFilePath} -> ${outputFilePath}`);
|
|
125
129
|
} catch (error) {
|
|
@@ -127,9 +131,34 @@ async function convertFile(inputFilePath, outputFilePath) {
|
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
async function interactiveMode() {
|
|
135
|
+
console.log('提示: 在这个模式下,您需要一次性输入所有 CSS 代码');
|
|
136
|
+
console.log('输入完成后按 Ctrl+D (Unix) 或 Ctrl+Z (Windows)\n');
|
|
137
|
+
|
|
138
|
+
console.log('请输入 CSS 代码:');
|
|
139
|
+
console.log('='.repeat(50));
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// 读取 stdin
|
|
143
|
+
const inputData = fs.readFileSync(0, 'utf8').trim();
|
|
144
|
+
|
|
145
|
+
if (!inputData) {
|
|
146
|
+
console.log('没有输入内容');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const output = await convertStyimat(inputData);
|
|
151
|
+
console.log('\n转换结果:');
|
|
152
|
+
console.log(output);
|
|
153
|
+
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('错误:', error.message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
130
159
|
async function convertAndOutput(inputContent, outputFile) {
|
|
131
160
|
try {
|
|
132
|
-
const outputContent = await
|
|
161
|
+
const outputContent = await convertStyimat(inputContent);
|
|
133
162
|
|
|
134
163
|
if (outputFile) {
|
|
135
164
|
fs.writeFileSync(outputFile, outputContent, 'utf8');
|
|
@@ -142,7 +171,7 @@ async function convertAndOutput(inputContent, outputFile) {
|
|
|
142
171
|
}
|
|
143
172
|
}
|
|
144
173
|
|
|
145
|
-
async function
|
|
174
|
+
async function convertStyimat(inputContent) {
|
|
146
175
|
// 尝试从多个位置加载 styimat.js
|
|
147
176
|
const possiblePaths = [
|
|
148
177
|
path.join(__dirname, 'styimat.js'),
|
|
@@ -150,11 +179,11 @@ async function convertHuecss(inputContent) {
|
|
|
150
179
|
path.join(process.cwd(), 'node_modules', 'styimat', 'styimat.js')
|
|
151
180
|
];
|
|
152
181
|
|
|
153
|
-
let
|
|
182
|
+
let styimatModule = null;
|
|
154
183
|
for (const modulePath of possiblePaths) {
|
|
155
184
|
try {
|
|
156
185
|
if (fs.existsSync(modulePath)) {
|
|
157
|
-
|
|
186
|
+
styimatModule = require(modulePath);
|
|
158
187
|
break;
|
|
159
188
|
}
|
|
160
189
|
} catch (e) {
|
|
@@ -162,19 +191,19 @@ async function convertHuecss(inputContent) {
|
|
|
162
191
|
}
|
|
163
192
|
}
|
|
164
193
|
|
|
165
|
-
if (!
|
|
194
|
+
if (!styimatModule) {
|
|
166
195
|
throw new Error('找不到 styimat.js 模块');
|
|
167
196
|
}
|
|
168
197
|
|
|
169
198
|
// 获取转换函数
|
|
170
|
-
const
|
|
199
|
+
const styimat = styimatModule.styimat || styimatModule;
|
|
171
200
|
|
|
172
|
-
if (typeof
|
|
201
|
+
if (typeof styimat.convert !== 'function') {
|
|
173
202
|
throw new Error('styimat.convert 不是函数');
|
|
174
203
|
}
|
|
175
204
|
|
|
176
205
|
// 执行转换
|
|
177
|
-
return await
|
|
206
|
+
return await styimat.convert(inputContent);
|
|
178
207
|
}
|
|
179
208
|
|
|
180
209
|
// 处理 promise 错误
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "styimat",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.2",
|
|
4
4
|
"description": "一个高效的CSS变量预处理库,支持Lab/LCH颜色空间自动转换、嵌套选择器和Display P3广色域,让现代CSS开发更简洁强大。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"homepage": "https://gitee.com/wxy6987/styimat#readme",
|
|
42
42
|
"scripts": {
|
|
43
43
|
"test": "echo \"No tests yet\" && exit 0",
|
|
44
|
-
"build:min": "npx
|
|
44
|
+
"build:min": "npx esbuild styimat.js --minify --outfile=styimat.min.js",
|
|
45
45
|
"prepare": "npm run build:min",
|
|
46
46
|
"prepublishOnly": "npm run build:min",
|
|
47
47
|
"postpublish": "npm run git:auto-commit && npm run git:auto-tag",
|
|
@@ -53,7 +53,9 @@
|
|
|
53
53
|
"git:auto-tag": "git tag -d v$(node -p \"require('./package.json').version\") 2>/dev/null || true && git tag -a v$(node -p \"require('./package.json').version\") -m 'Version $(node -p \"require('./package.json').version\")' && git push && git push --tags"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"chokidar": "^5.0.0"
|
|
57
|
-
|
|
56
|
+
"chokidar": "^5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"esbuild": "^0.27.2"
|
|
58
60
|
}
|
|
59
61
|
}
|
package/styimat.min.js
CHANGED
|
@@ -1,634 +1,29 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* MIT License
|
|
3
3
|
* Copyright (c) 2025 王小玗
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
// 导入文件缓存
|
|
31
|
-
const importCache=new Map;
|
|
32
|
-
/**
|
|
33
|
-
* 解析配置头
|
|
34
|
-
* @param {string} cssText - CSS文本
|
|
35
|
-
* @returns {Object} {config: 配置对象, css: 清理后的CSS}
|
|
36
|
-
*/function parseConfigHeader(cssText){const config={...defaultConfig};const lines=cssText.split("\n");const cleanLines=[];for(let line of lines){const trimmed=line.trim();
|
|
37
|
-
// 检查是否是配置行(以 # 开头)
|
|
38
|
-
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();
|
|
39
|
-
// 转换配置值
|
|
40
|
-
const configKey=camelCase(key);if(configKey in config){
|
|
41
|
-
// 根据值的类型进行转换
|
|
42
|
-
if(value==="true"||value==="false"){config[configKey]=value==="true"}else if(!isNaN(value)&&value.trim()!==""){
|
|
43
|
-
// 数字类型
|
|
44
|
-
config[configKey]=Number(value)}else{
|
|
45
|
-
// 字符串类型
|
|
46
|
-
config[configKey]=value}}else{console.warn(`未知的配置项: ${key}`)}}else{console.warn(`无效的配置行: ${trimmed}`)}}else{cleanLines.push(line)}}return{config:config,css:cleanLines.join("\n")}}
|
|
47
|
-
/**
|
|
48
|
-
* 将连字符分隔的字符串转换为驼峰命名
|
|
49
|
-
* @param {string} str - 输入字符串
|
|
50
|
-
* @returns {string} 驼峰命名字符串
|
|
51
|
-
*/function camelCase(str){return str.replace(/-([a-z])/g,function(match,letter){return letter.toUpperCase()})}
|
|
52
|
-
/**
|
|
53
|
-
* 检测浏览器是否支持 Display P3
|
|
54
|
-
* @returns {boolean} 是否支持P3
|
|
55
|
-
*/function detectP3Support(){if(p3Supported!==null)return p3Supported;if(typeof window==="undefined"||!window.CSS){p3Supported=false;return false}try{p3Supported=CSS.supports("color","color(display-p3 1 0 0)")}catch(error){console.warn("P3支持检测失败:",error);p3Supported=false}return p3Supported}
|
|
56
|
-
/**
|
|
57
|
-
* 解析一行CSS(可能多个语句在同一行,可能最后一个语句没有分号)
|
|
58
|
-
* 返回格式:[{属性名:值}, {属性名:值}, ...]
|
|
59
|
-
* @param {string} cssString - 要解析的CSS字符串
|
|
60
|
-
* @returns {Array} 包含属性名值对象的数组
|
|
61
|
-
*/function parseSingleLineCSS(cssString){
|
|
62
|
-
// 移除首尾空白字符
|
|
63
|
-
let str=cssString.trim();
|
|
64
|
-
// 如果以分号结尾,移除最后一个分号
|
|
65
|
-
if(str.endsWith(";")){str=str.slice(0,-1)}
|
|
66
|
-
// 如果字符串为空,返回空数组
|
|
67
|
-
if(!str){return[]}
|
|
68
|
-
// 分割多个CSS声明(以分号分隔)
|
|
69
|
-
const declarations=[];let currentDeclaration="";let inParens=0;// 记录括号嵌套层数,用于处理函数内的分号
|
|
70
|
-
let inQuotes=false;// 是否在引号内
|
|
71
|
-
let quoteChar="";// 当前引号字符
|
|
72
|
-
for(let i=0;i<str.length;i++){const char=str[i];
|
|
73
|
-
// 处理引号
|
|
74
|
-
if((char==='"'||char==="'")&&!inQuotes){inQuotes=true;quoteChar=char}else if(char===quoteChar&&inQuotes){inQuotes=false;quoteChar=""}
|
|
75
|
-
// 处理括号(不在引号内时)
|
|
76
|
-
if(!inQuotes){if(char==="("){inParens++}else if(char===")"){inParens--}}
|
|
77
|
-
// 如果遇到分号且不在括号和引号内,则分割声明
|
|
78
|
-
if(char===";"&&!inQuotes&&inParens===0){if(currentDeclaration.trim()){declarations.push(currentDeclaration.trim());currentDeclaration=""}}else{currentDeclaration+=char}}
|
|
79
|
-
// 添加最后一个声明(如果没有分号结尾)
|
|
80
|
-
if(currentDeclaration.trim()){declarations.push(currentDeclaration.trim())}
|
|
81
|
-
// 解析每个声明为对象
|
|
82
|
-
const result=[];for(const declaration of declarations){
|
|
83
|
-
// 跳过空声明
|
|
84
|
-
if(!declaration.trim())continue;
|
|
85
|
-
// 查找第一个冒号的位置
|
|
86
|
-
const colonIndex=findFirstColonOutsideQuotes(declaration);if(colonIndex===-1){console.warn(`无效的CSS声明: "${declaration}"`);continue}
|
|
87
|
-
// 分割属性名和值
|
|
88
|
-
const property=declaration.substring(0,colonIndex).trim();let value=declaration.substring(colonIndex+1).trim();
|
|
89
|
-
// 如果值以分号结尾,移除它(理论上不应该有,但处理一下)
|
|
90
|
-
if(value.endsWith(";")){value=value.slice(0,-1).trim()}
|
|
91
|
-
// 添加到结果数组
|
|
92
|
-
result.push({[property]:value})}return result}
|
|
93
|
-
/**
|
|
94
|
-
* 查找不在引号内的第一个冒号位置
|
|
95
|
-
* @param {string} str
|
|
96
|
-
* @returns {number}
|
|
97
|
-
*/function findFirstColonOutsideQuotes(str){let inQuotes=false;let quoteChar="";for(let i=0;i<str.length;i++){const char=str[i];
|
|
98
|
-
// 处理引号
|
|
99
|
-
if((char==='"'||char==="'")&&!inQuotes){inQuotes=true;quoteChar=char}else if(char===quoteChar&&inQuotes){inQuotes=false;quoteChar=""}
|
|
100
|
-
// 如果找到冒号且不在引号内,返回位置
|
|
101
|
-
if(char===":"&&!inQuotes){return i}}return-1}
|
|
102
|
-
/**
|
|
103
|
-
* 增强的数学表达式解析和计算
|
|
104
|
-
* @param {string} expression - 数学表达式
|
|
105
|
-
* @param {Object} config - 配置对象
|
|
106
|
-
* @returns {string} 计算结果
|
|
107
|
-
*/function evaluateMathExpression(expression,config){
|
|
108
|
-
// 如果禁用math()增强,则返回原始表达式
|
|
109
|
-
if(!config.enableMath){return`math(${expression})`}try{
|
|
110
|
-
// 清理表达式:移除空白字符
|
|
111
|
-
let cleanExpr=expression.replace(/\s+/g,"");
|
|
112
|
-
// 解析数学表达式
|
|
113
|
-
const result=parseMathExpression(cleanExpr,config);
|
|
114
|
-
// 如果结果包含单位,保留原始格式
|
|
115
|
-
if(hasUnits(cleanExpr)){
|
|
116
|
-
// 处理带单位的表达式
|
|
117
|
-
return processUnitExpression(cleanExpr,result)}else{
|
|
118
|
-
// 纯数字表达式,直接计算结果
|
|
119
|
-
return roundNumber(result,config.mathPrecision)}}catch(error){console.warn("math()表达式解析失败:",expression,error);return`math(${expression})`}}
|
|
120
|
-
/**
|
|
121
|
-
* 解析数学表达式
|
|
122
|
-
* @param {string} expr - 清理后的表达式
|
|
123
|
-
* @param {Object} config - 配置对象
|
|
124
|
-
* @returns {number} 计算结果
|
|
125
|
-
*/function parseMathExpression(expr,config){
|
|
126
|
-
// 处理括号
|
|
127
|
-
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)}
|
|
128
|
-
// 现在expr应该没有括号了
|
|
129
|
-
return evaluateSimpleExpression(expr,config)}
|
|
130
|
-
/**
|
|
131
|
-
* 评估简单表达式(无括号)
|
|
132
|
-
* @param {string} expr - 简单表达式
|
|
133
|
-
* @param {Object} config - 配置对象
|
|
134
|
-
* @returns {number} 计算结果
|
|
135
|
-
*/function evaluateSimpleExpression(expr,config){
|
|
136
|
-
// 处理操作符优先级:乘除优先于加减
|
|
137
|
-
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;
|
|
138
|
-
// 按照优先级处理操作符
|
|
139
|
-
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);
|
|
140
|
-
// 替换匹配的部分
|
|
141
|
-
expr=expr.substring(0,match.index)+result.value+expr.substring(match.index+match[0].length)}}
|
|
142
|
-
// 如果表达式无法进一步简化,尝试解析为数字
|
|
143
|
-
if(expr!==lastExpr){return evaluateSimpleExpression(expr,config)}
|
|
144
|
-
// 解析最终结果
|
|
145
|
-
const parsed=parseValueWithUnit(expr);return parsed.value}
|
|
146
|
-
/**
|
|
147
|
-
* 解析带单位的数值
|
|
148
|
-
* @param {string} str - 字符串值
|
|
149
|
-
* @returns {Object} {value: number, unit: string}
|
|
150
|
-
*/function parseValueWithUnit(str){
|
|
151
|
-
// 匹配数值和单位
|
|
152
|
-
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}}
|
|
153
|
-
/**
|
|
154
|
-
* 检查表达式是否包含单位
|
|
155
|
-
* @param {string} expr - 表达式
|
|
156
|
-
* @returns {boolean} 是否包含单位
|
|
157
|
-
*/function hasUnits(expr){return/[a-zA-Z%]/.test(expr)}
|
|
158
|
-
/**
|
|
159
|
-
* 处理带单位的表达式
|
|
160
|
-
* @param {string} originalExpr - 原始表达式
|
|
161
|
-
* @param {number} result - 计算结果
|
|
162
|
-
* @returns {string} 处理后的表达式
|
|
163
|
-
*/function processUnitExpression(originalExpr,result){
|
|
164
|
-
// 提取原始表达式中的单位
|
|
165
|
-
const unitMatch=originalExpr.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);if(unitMatch){return result+unitMatch[1]}
|
|
166
|
-
// 如果没有明确单位,检查是否为百分比
|
|
167
|
-
if(originalExpr.includes("%")){return result+"%"}
|
|
168
|
-
// 默认为像素
|
|
169
|
-
return result+"px"}
|
|
170
|
-
// 数学运算函数
|
|
171
|
-
function multiply(a,b){if(a.unit===b.unit||!a.unit&&!b.unit){return{value:a.value*b.value,unit:a.unit||b.unit}}
|
|
172
|
-
// 单位不匹配,返回原始表达式
|
|
173
|
-
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){
|
|
174
|
-
// 如 100px / 2
|
|
175
|
-
return{value:a.value/b.value,unit:a.unit}}else if(a.unit===b.unit){
|
|
176
|
-
// 如 100px / 2px
|
|
177
|
-
return{value:a.value/b.value,unit:""}}
|
|
178
|
-
// 单位不匹配,返回原始表达式
|
|
179
|
-
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}}
|
|
180
|
-
// 单位不匹配,返回原始表达式
|
|
181
|
-
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}}
|
|
182
|
-
// 单位不匹配,返回原始表达式
|
|
183
|
-
return{value:`${a.value}${a.unit}-${b.value}${b.unit}`,unit:""}}
|
|
184
|
-
/**
|
|
185
|
-
* 四舍五入到指定精度
|
|
186
|
-
* @param {number} num - 要四舍五入的数字
|
|
187
|
-
* @param {number} precision - 精度
|
|
188
|
-
* @returns {string} 四舍五入后的数字字符串
|
|
189
|
-
*/function roundNumber(num,precision=6){const factor=Math.pow(10,precision);const rounded=Math.round(num*factor)/factor;
|
|
190
|
-
// 移除不必要的尾随零
|
|
191
|
-
const str=rounded.toString();if(str.includes(".")){return str.replace(/(\.\d*?)0+$/,"$1").replace(/\.$/,"")}return str}
|
|
192
|
-
/**
|
|
193
|
-
* 处理所有的math()函数
|
|
194
|
-
* @param {string} cssValue - CSS属性值
|
|
195
|
-
* @param {Object} config - 配置对象
|
|
196
|
-
* @returns {string} 转换后的CSS值
|
|
197
|
-
*/function processMathFunctions(cssValue,config){if(!config.enableMath){return cssValue}let result=cssValue;
|
|
198
|
-
// 匹配math()函数
|
|
199
|
-
const mathRegex=/math\(([^)]+)\)/gi;
|
|
200
|
-
// 递归处理嵌套的math()函数
|
|
201
|
-
const processMath=str=>str.replace(mathRegex,(match,expression)=>evaluateMathExpression(expression,config));
|
|
202
|
-
// 递归处理,直到没有更多math()函数
|
|
203
|
-
let lastResult;do{lastResult=result;result=processMath(result)}while(result!==lastResult&&result.includes("math("));return result}
|
|
204
|
-
/**
|
|
205
|
-
* 解析并转换所有的 Lab 和 LCH 颜色(全局一次性处理)
|
|
206
|
-
* @param {string} cssValue - CSS属性值
|
|
207
|
-
* @param {Object} config - 配置对象
|
|
208
|
-
* @returns {string} 转换后的CSS值
|
|
209
|
-
*/function convertAllLabLchColors(cssValue,config){if(!config.convertLabToRGB&&!config.convertLchToRGB){return cssValue}let result=cssValue;
|
|
210
|
-
// 首先处理特殊的十六进制格式
|
|
211
|
-
result=parseHexColorFormats(result,config);
|
|
212
|
-
// 一次性处理所有的 lab() 和 lch() 函数
|
|
213
|
-
const colorFunctionRegex=/(lab|lch)\([^)]+\)/gi;
|
|
214
|
-
// 存储已经处理过的颜色字符串,避免重复处理
|
|
215
|
-
const processedColors=new Map;
|
|
216
|
-
// 使用一个函数来递归处理嵌套的颜色函数
|
|
217
|
-
const processColorFunctions=str=>str.replace(colorFunctionRegex,match=>{
|
|
218
|
-
// 如果这个颜色已经处理过,直接返回缓存结果
|
|
219
|
-
if(processedColors.has(match)){return processedColors.get(match)}let converted=match;
|
|
220
|
-
// 根据函数类型处理
|
|
221
|
-
if(match.toLowerCase().startsWith("lab(")){if(config.convertLabToRGB){converted=convertSingleLabColor(match,config)}}else if(match.toLowerCase().startsWith("lch(")){if(config.convertLchToRGB){converted=convertSingleLchColor(match,config)}}
|
|
222
|
-
// 缓存结果
|
|
223
|
-
processedColors.set(match,converted);return converted});
|
|
224
|
-
// 递归处理,直到没有更多颜色函数
|
|
225
|
-
let lastResult;do{lastResult=result;result=processColorFunctions(result)}while(result!==lastResult);return result}
|
|
226
|
-
/**
|
|
227
|
-
* 解析十六进制颜色格式:lab#L16A16B16 或 lch#L16C16H
|
|
228
|
-
* @param {string} value - 颜色值
|
|
229
|
-
* @param {Object} config - 配置对象
|
|
230
|
-
* @returns {string} 转换后的CSS颜色
|
|
231
|
-
*/function parseHexColorFormats(value,config){let result=value;
|
|
232
|
-
// 使用正则表达式一次性匹配所有 lab# 和 lch# 格式
|
|
233
|
-
const hexLabRegex=/lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi;const hexLchRegex=/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/gi;
|
|
234
|
-
// 存储处理过的十六进制颜色,避免重复处理
|
|
235
|
-
const processedHexColors=new Map;
|
|
236
|
-
// 处理 lab# 格式
|
|
237
|
-
result=result.replace(hexLabRegex,(match,L_hex,A_hex,B_hex)=>{if(processedHexColors.has(match)){return processedHexColors.get(match)}try{
|
|
238
|
-
// 将十六进制转换为十进制,并映射到Lab范围
|
|
239
|
-
const L=parseInt(L_hex,16)/255*100;// 0-100
|
|
240
|
-
const A=(parseInt(A_hex,16)-128)*1.5;// 大约 -192 到 192
|
|
241
|
-
const B=(parseInt(B_hex,16)-128)*1.5;// 大约 -192 到 192
|
|
242
|
-
const converted=generateColorString(L,A,B,config);processedHexColors.set(match,converted);return converted}catch(error){console.warn(`无法解析lab#十六进制颜色: ${match}`,error);return match}});
|
|
243
|
-
// 处理 lch# 格式
|
|
244
|
-
result=result.replace(hexLchRegex,(match,L_hex,C_hex,H_dec)=>{if(processedHexColors.has(match)){return processedHexColors.get(match)}try{
|
|
245
|
-
// 将十六进制转换为十进制,并映射到LCH范围
|
|
246
|
-
const L=parseInt(L_hex,16)/255*100;// 0-100
|
|
247
|
-
const C=parseInt(C_hex,16)/255*150;// 0-150
|
|
248
|
-
const H=parseInt(H_dec)/100*360;// 0-360
|
|
249
|
-
const lab=lchToLab(L,C,H);const converted=generateColorString(lab.L,lab.a,lab.b,config);processedHexColors.set(match,converted);return converted}catch(error){console.warn(`无法解析lch#十六进制颜色: ${match}`,error);return match}});return result}
|
|
250
|
-
/**
|
|
251
|
-
* 转换单个 lab() 颜色函数
|
|
252
|
-
* @param {string} labString - lab() 颜色字符串
|
|
253
|
-
* @param {Object} config - 配置对象
|
|
254
|
-
* @returns {string} 转换后的颜色字符串
|
|
255
|
-
*/function convertSingleLabColor(labString,config){
|
|
256
|
-
// 使用更精确的正则匹配 lab() 函数
|
|
257
|
-
const labRegex=/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i;const match=labString.match(labRegex);if(!match){return labString}try{
|
|
258
|
-
// 转换为数字
|
|
259
|
-
let L=parseFloat(match[1]);
|
|
260
|
-
// 如果L有百分号,转换为0-100范围的值
|
|
261
|
-
if(match[2]==="%"){L=L;// 百分比值直接使用 (0-100)
|
|
262
|
-
}else{
|
|
263
|
-
// 没有百分号,确保在0-100范围内
|
|
264
|
-
if(L<0)L=0;if(L>100)L=100}const A=parseFloat(match[3]);const B=parseFloat(match[4]);
|
|
265
|
-
// 处理 alpha 通道(如果有)
|
|
266
|
-
const alphaValue=match[5]!==undefined?match[5].includes("%")?parseFloat(match[5])/100:parseFloat(match[5]):null;return generateColorString(L,A,B,config,alphaValue)}catch(error){console.warn(`无法转换LAB颜色: ${labString}`,error);return labString}}
|
|
267
|
-
/**
|
|
268
|
-
* 转换单个 lch() 颜色函数
|
|
269
|
-
* @param {string} lchString - lch() 颜色字符串
|
|
270
|
-
* @param {Object} config - 配置对象
|
|
271
|
-
* @returns {string} 转换后的颜色字符串
|
|
272
|
-
*/function convertSingleLchColor(lchString,config){
|
|
273
|
-
// 使用更精确的正则匹配 lch() 函数
|
|
274
|
-
const lchRegex=/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i;const match=lchString.match(lchRegex);if(!match){return lchString}try{
|
|
275
|
-
// 转换为数字
|
|
276
|
-
let L=parseFloat(match[1]);
|
|
277
|
-
// 如果L有百分号,转换为0-100范围的值
|
|
278
|
-
if(match[2]==="%"){L=L;// 百分比值直接使用 (0-100)
|
|
279
|
-
}else{
|
|
280
|
-
// 没有百分号,确保在0-100范围内
|
|
281
|
-
if(L<0)L=0;if(L>100)L=100}const C=parseFloat(match[3]);let H=parseFloat(match[4]);
|
|
282
|
-
// 验证C范围:>= 0
|
|
283
|
-
if(C<0){console.warn(`LCH中的C值不能为负: ${C}`)}
|
|
284
|
-
// 验证H范围:0-360,有deg单位或无单位都是0-360
|
|
285
|
-
H=(H%360+360)%360;// 标准化到0-360
|
|
286
|
-
// LCH -> Lab
|
|
287
|
-
const lab=lchToLab(L,C,H);
|
|
288
|
-
// 处理 alpha 通道(如果有)
|
|
289
|
-
const alphaValue=match[6]!==undefined?match[6].includes("%")?parseFloat(match[6])/100:parseFloat(match[6]):null;return generateColorString(lab.L,lab.a,lab.b,config,alphaValue)}catch(error){console.warn(`无法转换LCH颜色: ${lchString}`,error);return lchString}}
|
|
290
|
-
/**
|
|
291
|
-
* LCH 转换为 Lab (CSS标准)
|
|
292
|
-
* @param {number} L - 明度 0-100 或 0%-100%
|
|
293
|
-
* @param {number} C - 色度 >=0
|
|
294
|
-
* @param {number} H - 色相角 0-360 度
|
|
295
|
-
* @returns {Object} Lab 值 {L, a, b}
|
|
296
|
-
*/function lchToLab(L,C,H){
|
|
297
|
-
// 角度转换为弧度
|
|
298
|
-
const H_rad=H*Math.PI/180;
|
|
299
|
-
// LCH -> Lab 转换公式
|
|
300
|
-
const a=C*Math.cos(H_rad);const b=C*Math.sin(H_rad);return{L:L,a:a,b:b}}
|
|
301
|
-
// 精确的 Lab -> sRGB 转换函数
|
|
302
|
-
function preciseLabToRGB(L,a,b){
|
|
303
|
-
// 1. Lab -> XYZ (D65白点)
|
|
304
|
-
const labToXyz=(L,a,b)=>{
|
|
305
|
-
// D65 白点参考值
|
|
306
|
-
const refX=.95047;const refY=1;const refZ=1.08883;const epsilon=.008856;const kappa=903.3;const fy=(L+16)/116;const fx=a/500+fy;const fz=fy-b/200;const xr=fx**3>epsilon?fx**3:(116*fx-16)/kappa;const yr=L>kappa*epsilon?((L+16)/116)**3:L/kappa;const zr=fz**3>epsilon?fz**3:(116*fz-16)/kappa;return[xr*refX,yr*refY,zr*refZ]};
|
|
307
|
-
// 2. XYZ -> Linear RGB
|
|
308
|
-
const xyzToLinearRgb=(x,y,z)=>{
|
|
309
|
-
// sRGB 转换矩阵 (D65)
|
|
310
|
-
const M=[[3.2404542,-1.5371385,-.4985314],[-.969266,1.8760108,.041556],[.0556434,-.2040259,1.0572252]];const r=M[0][0]*x+M[0][1]*y+M[0][2]*z;const g=M[1][0]*x+M[1][1]*y+M[1][2]*z;const b=M[2][0]*x+M[2][1]*y+M[2][2]*z;return[r,g,b]};
|
|
311
|
-
// 3. sRGB Gamma 校正
|
|
312
|
-
const applyGamma=c=>{const sign=c<0?-1:1;const absC=Math.abs(c);if(absC<=.0031308){return sign*12.92*absC}else{return sign*(1.055*Math.pow(absC,1/2.4)-.055)}};
|
|
313
|
-
// 4. 钳制到 [0, 255]
|
|
314
|
-
const clamp=value=>Math.max(0,Math.min(255,Math.round(value*255)));
|
|
315
|
-
// 执行转换
|
|
316
|
-
const[X,Y,Z]=labToXyz(L,a,b);const[linearR,linearG,linearB]=xyzToLinearRgb(X,Y,Z);const r=applyGamma(linearR);const g=applyGamma(linearG);const bOut=applyGamma(linearB);
|
|
317
|
-
// 返回钳制后的 RGB 值
|
|
318
|
-
return{r:clamp(r),g:clamp(g),b:clamp(bOut)}}
|
|
319
|
-
/**
|
|
320
|
-
* Lab -> Display P3 转换
|
|
321
|
-
* @param {number} L - 明度 0-100
|
|
322
|
-
* @param {number} a - a分量
|
|
323
|
-
* @param {number} b - b分量
|
|
324
|
-
* @returns {Object} P3颜色 {r, g, b}
|
|
325
|
-
*/function labToP3(L,a,b){
|
|
326
|
-
// 1. Lab -> XYZ (使用与RGB相同的转换)
|
|
327
|
-
const labToXyz=(L,a,b)=>{const refX=.95047;const refY=1;const refZ=1.08883;const epsilon=.008856;const kappa=903.3;const fy=(L+16)/116;const fx=a/500+fy;const fz=fy-b/200;const xr=fx**3>epsilon?fx**3:(116*fx-16)/kappa;const yr=L>kappa*epsilon?((L+16)/116)**3:L/kappa;const zr=fz**3>epsilon?fz**3:(116*fz-16)/kappa;return[xr*refX,yr*refY,zr*refZ]};
|
|
328
|
-
// 2. XYZ -> Linear P3
|
|
329
|
-
const xyzToLinearP3=(x,y,z)=>{
|
|
330
|
-
// Display P3 转换矩阵 (D65)
|
|
331
|
-
const M=[[2.493496911941425,-.9313836179191239,-.40271078445071684],[-.8294889695615747,1.7626640603183463,.023624685841943577],[.03584583024378447,-.07617238926804182,.9568845240076872]];const r=M[0][0]*x+M[0][1]*y+M[0][2]*z;const g=M[1][0]*x+M[1][1]*y+M[1][2]*z;const b=M[2][0]*x+M[2][1]*y+M[2][2]*z;return[r,g,b]};
|
|
332
|
-
// 3. P3 Gamma 校正(与sRGB相同)
|
|
333
|
-
const applyGamma=c=>{const sign=c<0?-1:1;const absC=Math.abs(c);if(absC<=.0031308){return sign*12.92*absC}else{return sign*(1.055*Math.pow(absC,1/2.4)-.055)}};
|
|
334
|
-
// 4. 钳制到 [0, 255],然后转换为 [0, 1] 范围
|
|
335
|
-
const clamp=value=>Math.max(0,Math.min(255,Math.round(value*255)));
|
|
336
|
-
// 执行转换
|
|
337
|
-
try{const[X,Y,Z]=labToXyz(L,a,b);const[linearR,linearG,linearB]=xyzToLinearP3(X,Y,Z);const r=applyGamma(linearR);const g=applyGamma(linearG);const bOut=applyGamma(linearB);
|
|
338
|
-
// 转换为0-1范围的浮点数用于P3颜色格式
|
|
339
|
-
return{r:Math.max(0,Math.min(1,r)),g:Math.max(0,Math.min(1,g)),b:Math.max(0,Math.min(1,bOut))}}catch(error){console.warn("P3转换失败:",error);const rgb=preciseLabToRGB(L,a,b);return{r:rgb.r/255,g:rgb.g/255,b:rgb.b/255}}}
|
|
340
|
-
/**
|
|
341
|
-
* 生成颜色字符串(根据P3支持情况输出P3或RGB)
|
|
342
|
-
* @param {number} L - Lab L值
|
|
343
|
-
* @param {number} a - Lab a值
|
|
344
|
-
* @param {number} b - Lab b值
|
|
345
|
-
* @param {Object} config - 配置对象
|
|
346
|
-
* @param {number|null} alpha - 透明度值
|
|
347
|
-
* @returns {string} CSS颜色字符串
|
|
348
|
-
*/function generateColorString(L,a,b,config,alpha=null){if(!config.enableP3||!detectP3Support()){
|
|
349
|
-
// 使用RGB
|
|
350
|
-
const rgb=preciseLabToRGB(L,a,b);if(alpha!==null){return`rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`}return`rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`}else{
|
|
351
|
-
// 使用P3
|
|
352
|
-
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)})`}}
|
|
353
|
-
/**
|
|
354
|
-
* 处理CSS值,根据配置转换LAB、LCH颜色和math()函数
|
|
355
|
-
* @param {string} value - CSS属性值
|
|
356
|
-
* @param {Object} config - 配置对象
|
|
357
|
-
* @returns {string} 处理后的值
|
|
358
|
-
*/function processCSSValue(value,config){let result=value;
|
|
359
|
-
// 1. 首先处理math()函数
|
|
360
|
-
if(config.enableMath){result=processMathFunctions(result,config)}
|
|
361
|
-
// 2. 然后处理LAB和LCH颜色
|
|
362
|
-
if(config.convertLabToRGB||config.convertLchToRGB){result=convertAllLabLchColors(result,config)}return result}
|
|
363
|
-
// 私有方法:提取变量定义并移除(支持嵌套作用域)
|
|
364
|
-
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();
|
|
365
|
-
// 检查是否是变量定义
|
|
366
|
-
const varMatch=trimmed.match(/^\$([a-zA-Z0-9_-]+)\s*:\s*(.+?);?$/);if(varMatch){const[,varName,varValue]=varMatch;
|
|
367
|
-
// 处理变量值中的数学表达式和颜色转换
|
|
368
|
-
const processedValue=processCSSValue(replaceVariableUsesInValue(varValue.trim(),{...globalVariables,...currentSelector?selectorVariables.get(currentSelector)||{}:{}}),config);if(currentSelector){
|
|
369
|
-
// 选择器内部的变量
|
|
370
|
-
if(!selectorVariables.has(currentSelector)){selectorVariables.set(currentSelector,{})}selectorVariables.get(currentSelector)[varName]=processedValue}else{
|
|
371
|
-
// 全局变量(:root 中)
|
|
372
|
-
globalVariables[varName]=processedValue}continue}
|
|
373
|
-
// 检查是否是选择器开始
|
|
374
|
-
if(trimmed.endsWith("{")){currentSelector=trimmed.slice(0,-1).trim();inSelectorBlock=true;cssWithoutVars+=line+"\n";continue}
|
|
375
|
-
// 检查是否是选择器结束
|
|
376
|
-
if(trimmed==="}"){if(inSelectorBlock){
|
|
377
|
-
// 在选择器结束前插入变量声明
|
|
378
|
-
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";
|
|
379
|
-
// 更新缩进级别
|
|
380
|
-
if(line.includes("{")){currentIndent+=config.indentSize}if(line.includes("}")){currentIndent=Math.max(0,currentIndent-config.indentSize)}}return{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars.trim()}}
|
|
381
|
-
// 修复的嵌套解析函数
|
|
382
|
-
function parseNestedRules(cssText,config){const lines=cssText.split("\n");const stack=[];// 存储规则信息:{selector, properties, children}
|
|
383
|
-
const rootRules=[];let currentIndent=0;for(let i=0;i<lines.length;i++){const line=lines[i];const trimmed=line.trim();if(!trimmed)continue;
|
|
384
|
-
// 计算缩进级别(假设使用空格缩进)
|
|
385
|
-
const indent=line.match(/^(\s*)/)[0].length;
|
|
386
|
-
// 处理规则块结束
|
|
387
|
-
if(trimmed==="}"){if(stack.length>0){const rule=stack.pop();
|
|
388
|
-
// 如果这个规则有父规则,添加到父规则的children中
|
|
389
|
-
if(stack.length>0){const parent=stack[stack.length-1];if(!parent.children)parent.children=[];parent.children.push(rule)}else{
|
|
390
|
-
// 否则是根级规则
|
|
391
|
-
rootRules.push(rule)}}continue}
|
|
392
|
-
// 处理规则开始
|
|
393
|
-
if(trimmed.endsWith("{")){const selector=trimmed.slice(0,-1).trim();
|
|
394
|
-
// 创建新规则
|
|
395
|
-
const rule={selector:selector,properties:[],children:[]};
|
|
396
|
-
// 推入堆栈
|
|
397
|
-
stack.push(rule);continue}
|
|
398
|
-
// 处理属性行(不包含 { 或 } 的行)
|
|
399
|
-
if(!trimmed.includes("{")&&!trimmed.includes("}")&&trimmed.includes(":")){if(stack.length>0){const currentRule=stack[stack.length-1];
|
|
400
|
-
// 处理属性值中的math()函数和颜色转换
|
|
401
|
-
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}}
|
|
402
|
-
// 处理栈中剩余规则
|
|
403
|
-
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)}}
|
|
404
|
-
// 将规则树转换为CSS字符串
|
|
405
|
-
return convertRulesToCSS(rootRules,config)}
|
|
406
|
-
// 将规则树转换为CSS字符串
|
|
407
|
-
function convertRulesToCSS(rules,config,parentSelector=""){let result="";const isParentAt=parentSelector.startsWith("@");for(const rule of rules){
|
|
408
|
-
// 构建完整选择器
|
|
409
|
-
const isAt=rule.selector.startsWith("@");let fullSelector=(isParentAt?" ".repeat(config.indentSize):"")+rule.selector;if(isAt){fullSelector=rule.selector+" {\n"}if(parentSelector){
|
|
410
|
-
// 处理 & 选择器
|
|
411
|
-
if(fullSelector.includes("&")){fullSelector=fullSelector.replace(/&/g,parentSelector)}else if(fullSelector.trim().startsWith(":")){fullSelector=parentSelector+fullSelector}else{fullSelector=parentSelector+(isParentAt?"":" ")+fullSelector}}
|
|
412
|
-
// 如果有属性,生成规则块
|
|
413
|
-
if(rule.properties.length>0){result+=(isAt?"":fullSelector)+" {\n";for(const prop of rule.properties){
|
|
414
|
-
// 再次处理属性值(确保math()函数和颜色被转换)
|
|
415
|
-
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"}
|
|
416
|
-
// 递归处理子规则
|
|
417
|
-
if(rule.children&&rule.children.length>0){result+=convertRulesToCSS(rule.children,config,fullSelector)}if(isParentAt){result+="}\n\n"}}return result.trim()+"\n\n"}
|
|
418
|
-
// 替换变量值中的变量引用
|
|
419
|
-
function replaceVariableUsesInValue(value,variables){return value.replace(/\$([a-zA-Z0-9_-]+)/g,(match,varName)=>{if(variables[varName]){return`var(--${varName})`}return match})}
|
|
420
|
-
// 替换变量使用
|
|
421
|
-
function replaceVariableUses(cssText,globalVariables,selectorVariables,config){let result=cssText;
|
|
422
|
-
// 处理变量
|
|
423
|
-
result=result.replace(/(?:\$\$|\$)([a-zA-Z0-9_-]+)/g,(match,varName)=>match.startsWith("$$")?`attr(${varName})`:`var(--${varName})`);
|
|
424
|
-
// 最后处理所有的LAB、LCH颜色和math()函数
|
|
425
|
-
result=processCSSValue(result,config);return result}
|
|
426
|
-
// 生成根规则
|
|
427
|
-
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" ".repeat(config.indentSize)+`--${name}: ${processedValue};`}).join("\n");return`${config.rootSelector} {\n${declarations}\n}\n\n`}
|
|
428
|
-
// 处理选择器内部的变量
|
|
429
|
-
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){
|
|
430
|
-
// 在选择器结束前插入变量声明
|
|
431
|
-
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}
|
|
432
|
-
// 使用全局处理函数处理所有math()和颜色
|
|
433
|
-
result+=processCSSValue(line,config)+"\n"}return result.trim()}
|
|
434
|
-
/**
|
|
435
|
-
* 处理 @import 语句
|
|
436
|
-
* @param {string} cssText - CSS文本
|
|
437
|
-
* @param {Object} config - 配置对象
|
|
438
|
-
* @returns {Promise<string>} 处理后的CSS文本
|
|
439
|
-
*/async function processImports(cssText,config){
|
|
440
|
-
// 匹配所有@import语句
|
|
441
|
-
const importRegex=/@import\s+(?:url\()?["']([^"']+)["'](?:\))?[^;]*;/g;
|
|
442
|
-
// 递归处理函数
|
|
443
|
-
const processRecursive=async text=>{
|
|
444
|
-
// 使用replace更简单的方式
|
|
445
|
-
const importPromises=[];
|
|
446
|
-
// 使用replace的回调函数收集所有import的promise
|
|
447
|
-
const processedText=text.replace(importRegex,(match,url)=>{const importPromise=fetchImportContent(url,config).then(importedCSS=>processImports(importedCSS,config)).catch(error=>{console.warn(`无法导入CSS文件: ${url}`,error);
|
|
448
|
-
// 导入失败时返回空字符串
|
|
449
|
-
return""});importPromises.push(importPromise);
|
|
450
|
-
// 返回占位符
|
|
451
|
-
return`__IMPORT_PLACEHOLDER_${importPromises.length-1}__`});
|
|
452
|
-
// 等待所有import完成
|
|
453
|
-
if(importPromises.length>0){const importedContents=await Promise.all(importPromises);
|
|
454
|
-
// 替换占位符为实际内容
|
|
455
|
-
let finalText=processedText;for(let i=0;i<importedContents.length;i++){finalText=finalText.replace(`__IMPORT_PLACEHOLDER_${i}__`,importedContents[i])}return finalText}return processedText};return processRecursive(cssText)}
|
|
456
|
-
/**
|
|
457
|
-
* 获取导入的CSS内容
|
|
458
|
-
* @param {string} url - CSS文件URL
|
|
459
|
-
* @param {Object} config - 配置对象
|
|
460
|
-
* @returns {Promise<string>} CSS内容
|
|
461
|
-
*/async function fetchImportContent(url,config){
|
|
462
|
-
// 检查缓存
|
|
463
|
-
if(config.importCache&&importCache.has(url)){return importCache.get(url)}
|
|
464
|
-
// 构建完整的URL
|
|
465
|
-
const fullUrl=config.importBaseUrl?new URL(url,config.importBaseUrl).href:url;let content;
|
|
466
|
-
// 检查是否为Node.js环境
|
|
467
|
-
const isNode=typeof process!=="undefined"&&process.versions&&process.versions.node;if(isNode&&typeof require!=="undefined"){
|
|
468
|
-
// Node.js环境:使用fs模块
|
|
469
|
-
try{const fs=require("fs");const path=require("path");
|
|
470
|
-
// 处理相对路径
|
|
471
|
-
let filePath=fullUrl;if(fullUrl.startsWith(".")){filePath=path.join(process.cwd(),fullUrl)}content=fs.readFileSync(filePath,"utf-8")}catch(error){throw new Error(`Node.js文件读取失败: ${fullUrl} - ${error.message}`)}}else{
|
|
472
|
-
// 浏览器环境:使用fetch
|
|
473
|
-
try{const controller=new AbortController;const timeoutId=setTimeout(()=>controller.abort(),config.importTimeout);const response=await fetch(fullUrl,{signal:controller.signal,headers:{Accept:"text/css"}});clearTimeout(timeoutId);if(!response.ok){throw new Error(`HTTP错误: ${response.status} ${response.statusText}`)}content=await response.text()}catch(error){if(error.name==="AbortError"){throw new Error(`导入超时: ${fullUrl}`)}throw new Error(`无法获取CSS: ${fullUrl} - ${error.message}`)}}
|
|
474
|
-
// 缓存内容
|
|
475
|
-
if(config.importCache){importCache.set(url,content)}return content}
|
|
476
|
-
/**
|
|
477
|
-
* 主转换函数
|
|
478
|
-
* @param {string} cssText - CSS文本
|
|
479
|
-
* @param {Object} customConfig - 自定义配置
|
|
480
|
-
* @returns {string|Promise<string>} 转换后的CSS(如果是异步则为Promise)
|
|
481
|
-
*/function convert(cssText,customConfig={}){
|
|
482
|
-
// 处理同步和异步版本
|
|
483
|
-
const syncConvert=(cssText,config)=>{
|
|
484
|
-
// 先解析配置头
|
|
485
|
-
const{config:headerConfig,css:cleanedCSS}=parseConfigHeader(cssText);const finalConfig={...defaultConfig,...customConfig,...headerConfig};
|
|
486
|
-
// 1. 提取变量定义(区分全局和选择器局部)
|
|
487
|
-
const{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars}=extractVariablesAndCSS(cleanedCSS,finalConfig);
|
|
488
|
-
// 2. 解析嵌套规则(如果启用)
|
|
489
|
-
let processedCSS=cssWithoutVars.trim();if(finalConfig.enableNesting&&cssWithoutVars.includes("{")){try{processedCSS=parseNestedRules(cssWithoutVars,finalConfig)}catch(error){console.warn("嵌套解析失败,使用原始CSS:",error)}}
|
|
490
|
-
// 3. 生成根规则(全局变量)
|
|
491
|
-
const rootRule=generateRootRule(globalVariables,finalConfig);
|
|
492
|
-
// 4. 注入选择器局部变量
|
|
493
|
-
let finalCSS=processedCSS;if(selectorVariables.size>0){finalCSS=injectSelectorVariables(processedCSS,selectorVariables,finalConfig)}
|
|
494
|
-
// 5. 替换变量使用
|
|
495
|
-
finalCSS=replaceVariableUses(finalCSS,globalVariables,selectorVariables,finalConfig);
|
|
496
|
-
// 6. 组合结果
|
|
497
|
-
return rootRule+finalCSS};
|
|
498
|
-
// 检查是否有@import语句(需要异步处理)
|
|
499
|
-
const hasImports=cssText&&/@import\s+(?:url\()?["']([^"']+)["']/i.test(cssText);const finalConfig={...defaultConfig,...customConfig};if(!hasImports){
|
|
500
|
-
// 没有@import,同步处理
|
|
501
|
-
return syncConvert(cssText,finalConfig)}else{
|
|
502
|
-
// 有@import,异步处理
|
|
503
|
-
return(async()=>{try{
|
|
504
|
-
// 处理@import
|
|
505
|
-
const processedWithImports=await processImports(cssText,finalConfig);
|
|
506
|
-
// 同步处理剩余部分
|
|
507
|
-
return syncConvert(processedWithImports,finalConfig)}catch(error){console.error("@import处理失败:",error);
|
|
508
|
-
// 如果导入失败,尝试同步处理原始CSS
|
|
509
|
-
return syncConvert(cleanedCSS,finalConfig)}})()}}
|
|
510
|
-
// 自动处理带有 e 属性的 style 标签
|
|
511
|
-
function autoProcessStyleTags(customConfig={}){const config={...defaultConfig,...customConfig};const styleTags=document.querySelectorAll(`style[${config.styleTagAttribute||"e"}]`);styleTags.forEach(styleTag=>{let originalCSS=styleTag.textContent;
|
|
512
|
-
// 处理@import(如果是异步的)
|
|
513
|
-
const processStyleTag=async()=>{try{if(styleTag.getAttribute("src")){originalCSS='@import url("'+styleTag.getAttribute("src")+'");'}const convertedCSS=await convert(originalCSS,config);
|
|
514
|
-
// 创建新的 style 标签
|
|
515
|
-
const newStyleTag=document.createElement("style");newStyleTag.textContent=convertedCSS;
|
|
516
|
-
// 插入到原标签后面
|
|
517
|
-
styleTag.parentNode.insertBefore(newStyleTag,styleTag.nextSibling);
|
|
518
|
-
// 可选:移除原标签
|
|
519
|
-
if(!config.preserveOriginal){styleTag.remove()}else{styleTag.style.display="none"}}catch(error){console.error("处理style标签失败:",error)}};processStyleTag()});return styleTags.length}function config(config={}){defaultConfig={...defaultConfig,...config}}
|
|
520
|
-
// 初始化自动处理
|
|
521
|
-
function apply(cssText,customConfig={}){const config={...defaultConfig,...customConfig};if(cssText){
|
|
522
|
-
// 检查是否有@import
|
|
523
|
-
const hasImports=/@import\s+(?:url\()?["']([^"']+)["']/i.test(cssText);if(hasImports){
|
|
524
|
-
// 异步处理
|
|
525
|
-
return(async()=>{try{const converted=await convert(cssText,config);const styleEl=document.createElement("style");styleEl.textContent=converted;document.head.appendChild(styleEl);return styleEl}catch(error){console.error("应用CSS失败:",error);return null}})()}else{
|
|
526
|
-
// 同步处理
|
|
527
|
-
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)}}}
|
|
528
|
-
// 返回公共 API
|
|
529
|
-
const api={convert:convert,apply:apply,config:config,version:"1.9.0",
|
|
530
|
-
// 检测P3支持
|
|
531
|
-
supportsP3:detectP3Support(),
|
|
532
|
-
// 重新检测P3支持(如果配置变化)
|
|
533
|
-
detectP3Support:detectP3Support,
|
|
534
|
-
// 导入相关方法
|
|
535
|
-
imports:{
|
|
536
|
-
/**
|
|
537
|
-
* 清除导入缓存
|
|
538
|
-
*/
|
|
539
|
-
clearCache:function(){importCache.clear()},
|
|
540
|
-
/**
|
|
541
|
-
* 设置导入基础URL
|
|
542
|
-
* @param {string} baseUrl - 基础URL
|
|
543
|
-
*/
|
|
544
|
-
setBaseUrl:function(baseUrl){defaultConfig.importBaseUrl=baseUrl},
|
|
545
|
-
/**
|
|
546
|
-
* 启用/禁用导入缓存
|
|
547
|
-
* @param {boolean} enabled - 是否启用缓存
|
|
548
|
-
*/
|
|
549
|
-
setCacheEnabled:function(enabled){defaultConfig.importCache=enabled},
|
|
550
|
-
/**
|
|
551
|
-
* 设置导入超时时间
|
|
552
|
-
* @param {number} timeout - 超时时间(毫秒)
|
|
553
|
-
*/
|
|
554
|
-
setTimeout:function(timeout){defaultConfig.importTimeout=timeout}},
|
|
555
|
-
// 数学计算工具方法
|
|
556
|
-
math:{
|
|
557
|
-
/**
|
|
558
|
-
* 计算数学表达式
|
|
559
|
-
* @param {string} expression - 数学表达式
|
|
560
|
-
* @returns {string} 计算结果
|
|
561
|
-
*/
|
|
562
|
-
evaluate:function(expression){return evaluateMathExpression(expression,defaultConfig)},
|
|
563
|
-
/**
|
|
564
|
-
* 解析带单位的数值
|
|
565
|
-
* @param {string} value - 带单位的字符串
|
|
566
|
-
* @returns {Object} {value: number, unit: string}
|
|
567
|
-
*/
|
|
568
|
-
parseUnit:parseValueWithUnit,
|
|
569
|
-
/**
|
|
570
|
-
* 四舍五入数字
|
|
571
|
-
* @param {number} num - 要四舍五入的数字
|
|
572
|
-
* @param {number} precision - 精度
|
|
573
|
-
* @returns {string} 四舍五入后的数字字符串
|
|
574
|
-
*/
|
|
575
|
-
round:roundNumber,
|
|
576
|
-
/**
|
|
577
|
-
* 测试数学表达式
|
|
578
|
-
* @param {string} expression - 要测试的表达式
|
|
579
|
-
* @param {Object} testConfig - 测试配置
|
|
580
|
-
* @returns {Object} 测试结果
|
|
581
|
-
*/
|
|
582
|
-
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}}}},
|
|
583
|
-
// 颜色转换工具方法
|
|
584
|
-
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)},
|
|
585
|
-
/**
|
|
586
|
-
* 生成颜色字符串(根据P3支持情况)
|
|
587
|
-
* @param {number} L - Lab L值
|
|
588
|
-
* @param {number} a - Lab a值
|
|
589
|
-
* @param {number} b - Lab b值
|
|
590
|
-
* @param {number|null} alpha - 透明度
|
|
591
|
-
* @param {boolean} useP3 - 是否使用P3(自动检测)
|
|
592
|
-
* @returns {string} CSS颜色字符串
|
|
593
|
-
*/
|
|
594
|
-
generateColor:function(L,a,b,alpha=null,useP3=true){return generateColorString(L,a,b,{enableP3:useP3},alpha)},
|
|
595
|
-
/**
|
|
596
|
-
* 解析CSS颜色字符串
|
|
597
|
-
* @param {string} colorString - CSS颜色字符串
|
|
598
|
-
* @returns {Object} 包含原始Lab值和颜色字符串的对象
|
|
599
|
-
*/
|
|
600
|
-
parseColor:function(colorString){try{
|
|
601
|
-
// 尝试解析lab()函数格式
|
|
602
|
-
const labMatch=colorString.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i);if(labMatch){let L=parseFloat(labMatch[1]);const A=parseFloat(labMatch[3]);const B=parseFloat(labMatch[4]);const alpha=labMatch[5]?labMatch[5].includes("%")?parseFloat(labMatch[5])/100:parseFloat(labMatch[5]):null;const colorStr=generateColorString(L,A,B,defaultConfig,alpha);return{L:L,A:A,B:B,alpha:alpha,rgb:preciseLabToRGB(L,A,B),p3:labToP3(L,A,B),colorString:colorStr}}
|
|
603
|
-
// 尝试解析lch()函数格式
|
|
604
|
-
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}}}};
|
|
605
|
-
// 创建一个可调用的主函数
|
|
606
|
-
const styimat=function(...args){
|
|
607
|
-
// 检查是否是模板字符串调用(标签函数)
|
|
608
|
-
if(args.length>1||args[0]&&args[0].raw){
|
|
609
|
-
// 处理模板字符串
|
|
610
|
-
return handleTemplateTag(...args)}
|
|
611
|
-
// 获取第一个参数
|
|
612
|
-
const firstArg=args[0];
|
|
613
|
-
// 如果传入CSS文本,则编译并返回结果
|
|
614
|
-
if(typeof firstArg==="string"){const result=convert(firstArg,{...defaultConfig,...args[1]});
|
|
615
|
-
// 如果结果是Promise,返回Promise
|
|
616
|
-
if(result&&typeof result.then==="function"){return result}return result}
|
|
617
|
-
// 如果传入配置对象,则应用配置
|
|
618
|
-
if(typeof firstArg==="object"&&firstArg!==null){defaultConfig={...defaultConfig,...firstArg};return styimat}
|
|
619
|
-
// 如果没有参数,执行自动处理
|
|
620
|
-
if(args.length===0){return apply()}return styimat};
|
|
621
|
-
// 处理模板字符串的函数
|
|
622
|
-
function handleTemplateTag(strings,...values){
|
|
623
|
-
// 拼接模板字符串
|
|
624
|
-
let cssText=strings[0];for(let i=0;i<values.length;i++){
|
|
625
|
-
// 处理插值(支持字符串、数字、函数等)
|
|
626
|
-
const value=values[i];let result="";if(typeof value==="function"){result=value()}else if(Array.isArray(value)){result=value.join(" ")}else{result=String(value!=null?value:"")}cssText+=result+strings[i+1]}
|
|
627
|
-
// 使用convert函数处理
|
|
628
|
-
const result=convert(cssText,defaultConfig);
|
|
629
|
-
// 如果结果是Promise,返回Promise
|
|
630
|
-
if(result&&typeof result.then==="function"){return result}return result}
|
|
631
|
-
// 将API的所有方法复制到主函数上
|
|
632
|
-
Object.assign(styimat,api);Object.setPrototypeOf(styimat,Function.prototype);
|
|
633
|
-
// 自动初始化
|
|
634
|
-
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},apply(target,thisArg,argumentsList){const prop=argumentsList[0];const value=argumentsList[1];const varName=prop.startsWith("--")?prop:`--${prop}`;if(value===undefined)return element.style.getPropertyValue(varName);element.style.setProperty(varName,value)}})}})}return styimat});
|
|
4
|
+
*/(function(d,C){typeof define=="function"&&define.amd?define([],C):typeof module=="object"&&module.exports?module.exports=C():d.styimat=C()})(typeof self<"u"?self:this,function(){let d={rootSelector:":root",variablePrefix:"--",preserveOriginal:!1,indentSize:4,enableNesting:!0,autoProcessStyleTags:!0,styleTagAttribute:"e",convertLabToRGB:!0,convertLchToRGB:!0,enableP3:!0,enableMath:!0,mathPrecision:6,importBaseUrl:"",importCache:!0,importTimeout:5e3},C=null;const F=new Map;function q(t){const e={...d},r=t.split(`
|
|
5
|
+
`),n=[];for(let o of r){const s=o.trim();if(s.startsWith("#")){const i=s.substring(1).trim(),c=i.indexOf(" ");if(c!==-1){const l=i.substring(0,c).trim(),a=i.substring(c+1).trim(),u=Y(l);u in e?a==="true"||a==="false"?e[u]=a==="true":!isNaN(a)&&a.trim()!==""?e[u]=Number(a):e[u]=a:console.warn(`\u672A\u77E5\u7684\u914D\u7F6E\u9879: ${l}`)}else console.warn(`\u65E0\u6548\u7684\u914D\u7F6E\u884C: ${s}`)}else n.push(o)}return{config:e,css:n.join(`
|
|
6
|
+
`)}}function Y(t){return t.replace(/-([a-z])/g,function(e,r){return r.toUpperCase()})}function z(){if(C!==null)return C;if(typeof window>"u"||!window.CSS)return C=!1,!1;try{C=CSS.supports("color","color(display-p3 1 0 0)")}catch(t){console.warn("P3\u652F\u6301\u68C0\u6D4B\u5931\u8D25:",t),C=!1}return C}function V(t){let e=t.trim();if(e.endsWith(";")&&(e=e.slice(0,-1)),!e)return[];const r=[];let n="",o=0,s=!1,i="";for(let l=0;l<e.length;l++){const a=e[l];(a==='"'||a==="'")&&!s?(s=!0,i=a):a===i&&s&&(s=!1,i=""),s||(a==="("?o++:a===")"&&o--),a===";"&&!s&&o===0?n.trim()&&(r.push(n.trim()),n=""):n+=a}n.trim()&&r.push(n.trim());const c=[];for(const l of r){if(!l.trim())continue;const a=D(l);if(a===-1){console.warn(`\u65E0\u6548\u7684CSS\u58F0\u660E: "${l}"`);continue}const u=l.substring(0,a).trim();let f=l.substring(a+1).trim();f.endsWith(";")&&(f=f.slice(0,-1).trim()),c.push({[u]:f})}return c}function D(t){let e=!1,r="";for(let n=0;n<t.length;n++){const o=t[n];if((o==='"'||o==="'")&&!e?(e=!0,r=o):o===r&&e&&(e=!1,r=""),o===":"&&!e)return n}return-1}function E(t,e){if(!e.enableMath)return`math(${t})`;try{let r=t.replace(/\s+/g,"");const n=_(r,e);return Q(r)?K(r,n):Z(n,e.mathPrecision)}catch(r){return console.warn("math()\u8868\u8FBE\u5F0F\u89E3\u6790\u5931\u8D25:",t,r),`math(${t})`}}function _(t,e){for(;t.includes("(")&&t.includes(")");){const r=t.lastIndexOf("("),n=t.indexOf(")",r);if(n===-1)break;const o=t.substring(r+1,n),s=_(o,e);t=t.substring(0,r)+s+t.substring(n+1)}return W(t,e)}function W(t,e){const r=[{regex:/([\d.]+(?:[a-zA-Z%]+)?)\*([\d.]+(?:[a-zA-Z%]+)?)/,handler:J},{regex:/([\d.]+(?:[a-zA-Z%]+)?)\/([\d.]+(?:[a-zA-Z%]+)?)/,handler:tt},{regex:/([\d.]+(?:[a-zA-Z%]+)?)\+([\d.]+(?:[a-zA-Z%]+)?)/,handler:et},{regex:/([\d.]+(?:[a-zA-Z%]+)?)-([\d.]+(?:[a-zA-Z%]+)?)/,handler:nt}];let n=t;for(const s of r){let i;for(;(i=t.match(s.regex))!==null;){const c=A(i[1]),l=A(i[2]),a=s.handler(c,l);t=t.substring(0,i.index)+a.value+t.substring(i.index+i[0].length)}}return t!==n?W(t,e):A(t).value}function A(t){const e=t.match(/^([\d.]+)([a-zA-Z%]*)$/);if(!e)throw new Error(`\u65E0\u6CD5\u89E3\u6790\u503C: ${t}`);const r=parseFloat(e[1]),n=e[2]||"";return{value:r,unit:n}}function Q(t){return/[a-zA-Z%]/.test(t)}function K(t,e){const r=t.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);return r?e+r[1]:t.includes("%")?e+"%":e+"px"}function J(t,e){return t.unit===e.unit||!t.unit&&!e.unit?{value:t.value*e.value,unit:t.unit||e.unit}:{value:`${t.value}${t.unit}*${e.value}${e.unit}`,unit:""}}function tt(t,e){if(e.value===0)throw new Error("\u9664\u4EE5\u96F6");return t.unit&&!e.unit?{value:t.value/e.value,unit:t.unit}:t.unit===e.unit?{value:t.value/e.value,unit:""}:{value:`${t.value}${t.unit}/${e.value}${e.unit}`,unit:""}}function et(t,e){return t.unit===e.unit?{value:t.value+e.value,unit:t.unit}:{value:`${t.value}${t.unit}+${e.value}${e.unit}`,unit:""}}function nt(t,e){return t.unit===e.unit?{value:t.value-e.value,unit:t.unit}:{value:`${t.value}${t.unit}-${e.value}${e.unit}`,unit:""}}function Z(t,e=6){const r=Math.pow(10,e),o=(Math.round(t*r)/r).toString();return o.includes(".")?o.replace(/(\.\d*?)0+$/,"$1").replace(/\.$/,""):o}function rt(t,e){if(!e.enableMath)return t;let r=t;const n=/math\(([^)]+)\)/gi,o=i=>i.replace(n,(c,l)=>E(l,e));let s;do s=r,r=o(r);while(r!==s&&r.includes("math("));return r}function st(t,e){if(!e.convertLabToRGB&&!e.convertLchToRGB)return t;let r=t;r=ot(r,e);const n=/(lab|lch)\([^)]+\)/gi,o=new Map,s=c=>c.replace(n,l=>{if(o.has(l))return o.get(l);let a=l;return l.toLowerCase().startsWith("lab(")?e.convertLabToRGB&&(a=ct(l,e)):l.toLowerCase().startsWith("lch(")&&e.convertLchToRGB&&(a=it(l,e)),o.set(l,a),a});let i;do i=r,r=s(r);while(r!==i);return r}function ot(t,e){let r=t;const n=/lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi,o=/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/gi,s=new Map;return r=r.replace(n,(i,c,l,a)=>{if(s.has(i))return s.get(i);try{const u=parseInt(c,16)/255*100,f=(parseInt(l,16)-128)*1.5,p=(parseInt(a,16)-128)*1.5,h=L(u,f,p,e);return s.set(i,h),h}catch(u){return console.warn(`\u65E0\u6CD5\u89E3\u6790lab#\u5341\u516D\u8FDB\u5236\u989C\u8272: ${i}`,u),i}}),r=r.replace(o,(i,c,l,a)=>{if(s.has(i))return s.get(i);try{const u=parseInt(c,16)/255*100,f=parseInt(l,16)/255*150,p=parseInt(a)/100*360,h=w(u,f,p),g=L(h.L,h.a,h.b,e);return s.set(i,g),g}catch(u){return console.warn(`\u65E0\u6CD5\u89E3\u6790lch#\u5341\u516D\u8FDB\u5236\u989C\u8272: ${i}`,u),i}}),r}function ct(t,e){const r=/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i,n=t.match(r);if(!n)return t;try{let o=parseFloat(n[1]);n[2]==="%"?o=o:(o<0&&(o=0),o>100&&(o=100));const s=parseFloat(n[3]),i=parseFloat(n[4]),c=n[5]!==void 0?n[5].includes("%")?parseFloat(n[5])/100:parseFloat(n[5]):null;return L(o,s,i,e,c)}catch(o){return console.warn(`\u65E0\u6CD5\u8F6C\u6362LAB\u989C\u8272: ${t}`,o),t}}function it(t,e){const r=/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i,n=t.match(r);if(!n)return t;try{let o=parseFloat(n[1]);n[2]==="%"?o=o:(o<0&&(o=0),o>100&&(o=100));const s=parseFloat(n[3]);let i=parseFloat(n[4]);s<0&&console.warn(`LCH\u4E2D\u7684C\u503C\u4E0D\u80FD\u4E3A\u8D1F: ${s}`),i=(i%360+360)%360;const c=w(o,s,i),l=n[6]!==void 0?n[6].includes("%")?parseFloat(n[6])/100:parseFloat(n[6]):null;return L(c.L,c.a,c.b,e,l)}catch(o){return console.warn(`\u65E0\u6CD5\u8F6C\u6362LCH\u989C\u8272: ${t}`,o),t}}function w(t,e,r){const n=r*Math.PI/180,o=e*Math.cos(n),s=e*Math.sin(n);return{L:t,a:o,b:s}}function S(t,e,r){const n=(m,y,v)=>{const X=(m+16)/116,B=y/500+X,O=X-v/200,bt=B**3>.008856?B**3:(116*B-16)/903.3,yt=m>903.3*.008856?((m+16)/116)**3:m/903.3,$t=O**3>.008856?O**3:(116*O-16)/903.3;return[bt*.95047,yt*1,$t*1.08883]},o=(m,y,v)=>{const $=[[3.2404542,-1.5371385,-.4985314],[-.969266,1.8760108,.041556],[.0556434,-.2040259,1.0572252]],T=$[0][0]*m+$[0][1]*y+$[0][2]*v,N=$[1][0]*m+$[1][1]*y+$[1][2]*v,U=$[2][0]*m+$[2][1]*y+$[2][2]*v;return[T,N,U]},s=m=>{const y=m<0?-1:1,v=Math.abs(m);return v<=.0031308?y*12.92*v:y*(1.055*Math.pow(v,.4166666666666667)-.055)},i=m=>Math.max(0,Math.min(255,Math.round(m*255))),[c,l,a]=n(t,e,r),[u,f,p]=o(c,l,a),h=s(u),g=s(f),b=s(p);return{r:i(h),g:i(g),b:i(b)}}function R(t,e,r){const n=(c,l,a)=>{const b=(c+16)/116,m=l/500+b,y=b-a/200,v=m**3>.008856?m**3:(116*m-16)/903.3,$=c>903.3*.008856?((c+16)/116)**3:c/903.3,T=y**3>.008856?y**3:(116*y-16)/903.3;return[v*.95047,$*1,T*1.08883]},o=(c,l,a)=>{const u=[[2.493496911941425,-.9313836179191239,-.40271078445071684],[-.8294889695615747,1.7626640603183463,.023624685841943577],[.03584583024378447,-.07617238926804182,.9568845240076872]],f=u[0][0]*c+u[0][1]*l+u[0][2]*a,p=u[1][0]*c+u[1][1]*l+u[1][2]*a,h=u[2][0]*c+u[2][1]*l+u[2][2]*a;return[f,p,h]},s=c=>{const l=c<0?-1:1,a=Math.abs(c);return a<=.0031308?l*12.92*a:l*(1.055*Math.pow(a,.4166666666666667)-.055)},i=c=>Math.max(0,Math.min(255,Math.round(c*255)));try{const[c,l,a]=n(t,e,r),[u,f,p]=o(c,l,a),h=s(u),g=s(f),b=s(p);return{r:Math.max(0,Math.min(1,h)),g:Math.max(0,Math.min(1,g)),b:Math.max(0,Math.min(1,b))}}catch(c){console.warn("P3\u8F6C\u6362\u5931\u8D25:",c);const l=S(t,e,r);return{r:l.r/255,g:l.g/255,b:l.b/255}}}function L(t,e,r,n,o=null){if(!n.enableP3||!z()){const s=S(t,e,r);return o!==null?`rgba(${s.r}, ${s.g}, ${s.b}, ${o})`:`rgb(${s.r}, ${s.g}, ${s.b})`}else{const s=R(t,e,r);return o!==null?`color(display-p3 ${s.r.toFixed(4)} ${s.g.toFixed(4)} ${s.b.toFixed(4)} / ${o})`:`color(display-p3 ${s.r.toFixed(4)} ${s.g.toFixed(4)} ${s.b.toFixed(4)})`}}function M(t,e){let r=t;return e.enableMath&&(r=rt(r,e)),(e.convertLabToRGB||e.convertLchToRGB)&&(r=st(r,e)),r}function lt(t,e){const r=t.split(`
|
|
7
|
+
`),n={},o=new Map;let s="",i=null,c=!1,l=0;for(let a of r){const u=a.trim(),f=u.match(/^\$([a-zA-Z0-9_-]+)\s*:\s*(.+?);?$/);if(f){const[,p,h]=f,g=M(H(h.trim(),{...n,...i?o.get(i)||{}:{}}),e);i?(o.has(i)||o.set(i,{}),o.get(i)[p]=g):n[p]=g;continue}if(u.endsWith("{")){i=u.slice(0,-1).trim(),c=!0,s+=a+`
|
|
8
|
+
`;continue}if(u==="}"){if(c&&i&&o.has(i)){const p=o.get(i),h=" ".repeat(l);for(const[g,b]of Object.entries(p))s+=`${h} --${g}: ${b};
|
|
9
|
+
`}c=!1,i=null}s+=a+`
|
|
10
|
+
`,a.includes("{")&&(l+=e.indentSize),a.includes("}")&&(l=Math.max(0,l-e.indentSize))}return{globalVariables:n,selectorVariables:o,cssWithoutVars:s.trim()}}function at(t,e){const r=t.split(`
|
|
11
|
+
`),n=[],o=[];let s=0;for(let i=0;i<r.length;i++){const c=r[i],l=c.trim();if(!l)continue;const a=c.match(/^(\s*)/)[0].length;if(l==="}"){if(n.length>0){const u=n.pop();if(n.length>0){const f=n[n.length-1];f.children||(f.children=[]),f.children.push(u)}else o.push(u)}continue}if(l.endsWith("{")){const f={selector:l.slice(0,-1).trim(),properties:[],children:[]};n.push(f);continue}if(!l.includes("{")&&!l.includes("}")&&l.includes(":")){if(n.length>0){const u=n[n.length-1];V(l).forEach(p=>{const h=Object.keys(p)[0],g=M(p[h],e);u.properties.push(`${h}: ${g}`)})}continue}}for(;n.length>0;){const i=n.pop();if(n.length===0)o.push(i);else{const c=n[n.length-1];c.children||(c.children=[]),c.children.push(i)}}return k(o,e)}function k(t,e,r=""){let n="";const o=r.startsWith("@");for(const s of t){const i=s.selector.startsWith("@");let c=(o?" ".repeat(e.indentSize):"")+s.selector;if(i&&(c=s.selector+` {
|
|
12
|
+
`),r&&(c.includes("&")?c=c.replace(/&/g,r):c.trim().startsWith(":")?c=r+c:c=r+(o?"":" ")+c),s.properties.length>0){n+=(i?"":c)+` {
|
|
13
|
+
`;for(const l of s.properties)V(l).forEach(u=>{const f=Object.keys(u)[0],p=M(u[f],e);n+=(o?" ".repeat(e.indentSize):"")+" ".repeat(e.indentSize)+`${f}: ${p};
|
|
14
|
+
`});n+=o?" ".repeat(e.indentSize)+`}
|
|
15
|
+
`:`}
|
|
16
|
+
|
|
17
|
+
`}s.children&&s.children.length>0&&(n+=k(s.children,e,c)),o&&(n+=`}
|
|
18
|
+
|
|
19
|
+
`)}return n.trim()+`
|
|
20
|
+
|
|
21
|
+
`}function H(t,e){return t.replace(/\$([a-zA-Z0-9_-]+)/g,(r,n)=>e[n]?`var(--${n})`:r)}function ut(t,e,r,n){let o=t;return o=o.replace(/(?:\$\$|\$)([a-zA-Z0-9_-]+)/g,(s,i)=>s.startsWith("$$")?`attr(${i})`:`var(--${i})`),o=M(o,n),o}function ft(t,e){if(Object.keys(t).length===0)return"";const r=Object.entries(t).map(([n,o])=>{const s=M(H(o,t),e);return" ".repeat(e.indentSize)+`--${n}: ${s};`}).join(`
|
|
22
|
+
`);return`${e.rootSelector} {
|
|
23
|
+
${r}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
`}function pt(t,e,r){let n="";const o=t.split(`
|
|
27
|
+
`);let s=null;for(let i of o){const c=i.trim();if(c.endsWith("{")&&(s=c.slice(0,-1).trim()),c==="}"&&s){if(e.has(s)){const l=e.get(s),a=i.match(/^(\s*)/)[0];for(const[u,f]of Object.entries(l))n+=" --"+u+": "+f+`;
|
|
28
|
+
`}s=null}n+=M(i,r)+`
|
|
29
|
+
`}return n.trim()}async function j(t,e){const r=/@import\s+(?:url\()?["']([^"']+)["'](?:\))?[^;]*;/g;return(async o=>{const s=[],i=o.replace(r,(c,l)=>{const a=ht(l,e).then(u=>j(u,e)).catch(u=>(console.warn(`\u65E0\u6CD5\u5BFC\u5165CSS\u6587\u4EF6: ${l}`,u),""));return s.push(a),`__IMPORT_PLACEHOLDER_${s.length-1}__`});if(s.length>0){const c=await Promise.all(s);let l=i;for(let a=0;a<c.length;a++)l=l.replace(`__IMPORT_PLACEHOLDER_${a}__`,c[a]);return l}return i})(t)}async function ht(t,e){if(e.importCache&&F.has(t))return F.get(t);const r=e.importBaseUrl?new URL(t,e.importBaseUrl).href:t;let n;if(typeof process<"u"&&process.versions&&process.versions.node&&typeof require<"u")try{const s=require("fs"),i=require("path");let c=r;r.startsWith(".")&&(c=i.join(process.cwd(),r)),n=s.readFileSync(c,"utf-8")}catch(s){throw new Error(`Node.js\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25: ${r} - ${s.message}`)}else try{const s=new AbortController,i=setTimeout(()=>s.abort(),e.importTimeout),c=await fetch(r,{signal:s.signal,headers:{Accept:"text/css"}});if(clearTimeout(i),!c.ok)throw new Error(`HTTP\u9519\u8BEF: ${c.status} ${c.statusText}`);n=await c.text()}catch(s){throw s.name==="AbortError"?new Error(`\u5BFC\u5165\u8D85\u65F6: ${r}`):new Error(`\u65E0\u6CD5\u83B7\u53D6CSS: ${r} - ${s.message}`)}return e.importCache&&F.set(t,n),n}function x(t,e={}){const r=(s,i)=>{const{config:c,css:l}=q(s),a={...d,...e,...c},{globalVariables:u,selectorVariables:f,cssWithoutVars:p}=lt(l,a);let h=p.trim();if(a.enableNesting&&p.includes("{"))try{h=at(p,a)}catch(m){console.warn("\u5D4C\u5957\u89E3\u6790\u5931\u8D25\uFF0C\u4F7F\u7528\u539F\u59CBCSS:",m)}const g=ft(u,a);let b=h;return f.size>0&&(b=pt(h,f,a)),b=ut(b,u,f,a),g+b},n=t&&/@import\s+(?:url\()?["']([^"']+)["']/i.test(t),o={...d,...e};return n?(async()=>{try{const s=await j(t,o);return r(s,o)}catch(s){return console.error("@import\u5904\u7406\u5931\u8D25:",s),r(cleanedCSS,o)}})():r(t,o)}function G(t={}){const e={...d,...t},r=document.querySelectorAll(`style[${e.styleTagAttribute||"e"}]`);return r.forEach(n=>{let o=n.textContent;(async()=>{try{n.getAttribute("src")&&(o='@import url("'+n.getAttribute("src")+'");');const i=await x(o,e),c=document.createElement("style");c.textContent=i,n.parentNode.insertBefore(c,n.nextSibling),e.preserveOriginal?n.style.display="none":n.remove()}catch(i){console.error("\u5904\u7406style\u6807\u7B7E\u5931\u8D25:",i)}})()}),r.length}function dt(t={}){d={...d,...t}}function I(t,e={}){const r={...d,...e};if(t){if(/@import\s+(?:url\()?["']([^"']+)["']/i.test(t))return(async()=>{try{const o=await x(t,r),s=document.createElement("style");return s.textContent=o,document.head.appendChild(s),s}catch(o){return console.error("\u5E94\u7528CSS\u5931\u8D25:",o),null}})();{const o=x(t,r),s=document.createElement("style");return s.textContent=o,document.head.appendChild(s),s}}else document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{G(r)}):G(r)}const mt={convert:x,apply:I,config:dt,version:"1.9.0",supportsP3:z(),detectP3Support:z,imports:{clearCache:function(){F.clear()},setBaseUrl:function(t){d.importBaseUrl=t},setCacheEnabled:function(t){d.importCache=t},setTimeout:function(t){d.importTimeout=t}},math:{evaluate:function(t){return E(t,d)},parseUnit:A,round:Z,test:function(t,e={}){const r={...d,...e};try{const n=E(t,r);return{success:!0,expression:t,result:n,parsed:A(n.replace(/^calc\(|\)$/g,""))}}catch(n){return{success:!1,expression:t,error:n.message}}}},colorUtils:{labToRGB:S,lchToLab:w,lchToRGB:function(t,e,r){const n=w(t,e,r);return S(n.L,n.a,n.b)},labToP3:R,lchToP3:function(t,e,r){const n=w(t,e,r);return R(n.L,n.a,n.b)},parseHexLab:function(t){const e=t.match(/lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i);if(!e)return null;const r=e[1],n=e[2],o=e[3],s=parseInt(r,16)/255*100,i=(parseInt(n,16)-128)*1.5,c=(parseInt(o,16)-128)*1.5;return S(s,i,c)},parseHexLch:function(t){const e=t.match(/lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/i);if(!e)return null;const r=e[1],n=e[2],o=e[3],s=parseInt(r,16)/255*100,i=parseInt(n,16)/255*150,c=parseInt(o)/100*360,l=w(s,i,c);return S(l.L,l.a,l.b)},generateColor:function(t,e,r,n=null,o=!0){return L(t,e,r,{enableP3:o},n)},parseColor:function(t){try{const e=t.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i);if(e){let n=parseFloat(e[1]);const o=parseFloat(e[3]),s=parseFloat(e[4]),i=e[5]?e[5].includes("%")?parseFloat(e[5])/100:parseFloat(e[5]):null,c=L(n,o,s,d,i);return{L:n,A:o,B:s,alpha:i,rgb:S(n,o,s),p3:R(n,o,s),colorString:c}}const r=t.match(/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i);if(r){let n=parseFloat(r[1]);const o=parseFloat(r[3]);let s=parseFloat(r[4]);const i=r[6]?r[6].includes("%")?parseFloat(r[6])/100:parseFloat(r[6]):null,c=w(n,o,s),l=L(c.L,c.a,c.b,d,i);return{L:n,C:o,H:s,alpha:i,lab:c,rgb:S(c.L,c.a,c.b),p3:R(c.L,c.a,c.b),colorString:l}}return null}catch(e){return console.warn("\u65E0\u6CD5\u89E3\u6790\u989C\u8272:",t,e),null}}}},P=function(...t){if(t.length>1||t[0]&&t[0].raw)return gt(...t);const e=t[0];if(typeof e=="string"){const r=x(e,{...d,...t[1]});return r&&typeof r.then=="function",r}return typeof e=="object"&&e!==null?(d={...d,...e},P):t.length===0?I():P};function gt(t,...e){let r=t[0];for(let o=0;o<e.length;o++){const s=e[o];let i="";typeof s=="function"?i=s():Array.isArray(s)?i=s.join(" "):i=String(s??""),r+=i+t[o+1]}const n=x(r,d);return n&&typeof n.then=="function",n}return Object.assign(P,mt),Object.setPrototypeOf(P,Function.prototype),typeof window<"u"&&(I(),Object.defineProperty(window.HTMLElement.prototype,"cssVar",{get(){const t=this;return new Proxy(()=>{},{get(e,r){const n=r.startsWith("--")?r:`--${r}`;return t.style.getPropertyValue(n)},set(e,r,n){const o=r.startsWith("--")?r:`--${r}`;return t.style.setProperty(o,n),!0},apply(e,r,n){const o=n[0],s=n[1],i=o.startsWith("--")?o:`--${o}`;if(s===void 0)return t.style.getPropertyValue(i);t.style.setProperty(i,s)}})}})),P});
|