styimat 4.0.0 → 4.2.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 +36 -14
- package/dist/styimat.js +438 -365
- package/dist/styimat.min.js +25 -20
- package/dist/styimat.min.mjs +25 -20
- package/dist/styimat.mjs +439 -366
- package/package.json +4 -2
package/dist/styimat.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* 增强 math() 函数,支持复杂数学计算
|
|
12
12
|
* 支持配置头
|
|
13
13
|
* 支持 @import 语法
|
|
14
|
+
* 支持 @alias 语法
|
|
15
|
+
* 支持 @macro 宏定义系统
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
18
|
const styimat = (function() {
|
|
@@ -26,11 +28,13 @@ const styimat = (function() {
|
|
|
26
28
|
convertLabToRGB: true,
|
|
27
29
|
convertLchToRGB: true,
|
|
28
30
|
enableP3: true,
|
|
29
|
-
enableMath: true,
|
|
30
|
-
mathPrecision: 6,
|
|
31
|
-
importBaseUrl: '',
|
|
32
|
-
importCache: true,
|
|
33
|
-
importTimeout: 5000,
|
|
31
|
+
enableMath: true,
|
|
32
|
+
mathPrecision: 6,
|
|
33
|
+
importBaseUrl: '',
|
|
34
|
+
importCache: true,
|
|
35
|
+
importTimeout: 5000,
|
|
36
|
+
enableAlias: true,
|
|
37
|
+
enableMacros: true,
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
// 全局P3支持检测结果
|
|
@@ -38,11 +42,15 @@ const styimat = (function() {
|
|
|
38
42
|
|
|
39
43
|
// 导入文件缓存
|
|
40
44
|
const importCache = new Map();
|
|
45
|
+
|
|
46
|
+
// 别名映射表
|
|
47
|
+
const aliasMap = new Map();
|
|
48
|
+
|
|
49
|
+
// 宏定义存储
|
|
50
|
+
const macroRegistry = new Map();
|
|
41
51
|
|
|
42
52
|
/**
|
|
43
53
|
* 解析配置头
|
|
44
|
-
* @param {string} cssText - CSS文本
|
|
45
|
-
* @returns {Object} {config: 配置对象, css: 清理后的CSS}
|
|
46
54
|
*/
|
|
47
55
|
function parseConfigHeader(cssText) {
|
|
48
56
|
const config = { ...defaultConfig };
|
|
@@ -52,7 +60,6 @@ const styimat = (function() {
|
|
|
52
60
|
for (let line of lines) {
|
|
53
61
|
const trimmed = line.trim();
|
|
54
62
|
|
|
55
|
-
// 检查是否是配置行(以 # 开头)
|
|
56
63
|
if (trimmed.startsWith('#')) {
|
|
57
64
|
const configLine = trimmed.substring(1).trim();
|
|
58
65
|
const firstSpace = configLine.indexOf(' ');
|
|
@@ -61,18 +68,14 @@ const styimat = (function() {
|
|
|
61
68
|
const key = configLine.substring(0, firstSpace).trim();
|
|
62
69
|
const value = configLine.substring(firstSpace + 1).trim();
|
|
63
70
|
|
|
64
|
-
// 转换配置值
|
|
65
71
|
const configKey = camelCase(key);
|
|
66
72
|
|
|
67
73
|
if (configKey in config) {
|
|
68
|
-
// 根据值的类型进行转换
|
|
69
74
|
if (value === 'true' || value === 'false') {
|
|
70
75
|
config[configKey] = value === 'true';
|
|
71
76
|
} else if (!isNaN(value) && value.trim() !== '') {
|
|
72
|
-
// 数字类型
|
|
73
77
|
config[configKey] = Number(value);
|
|
74
78
|
} else {
|
|
75
|
-
// 字符串类型
|
|
76
79
|
config[configKey] = value;
|
|
77
80
|
}
|
|
78
81
|
} else {
|
|
@@ -92,10 +95,266 @@ const styimat = (function() {
|
|
|
92
95
|
};
|
|
93
96
|
}
|
|
94
97
|
|
|
98
|
+
/**
|
|
99
|
+
* 解析 @alias 语句
|
|
100
|
+
*/
|
|
101
|
+
function parseAliasStatements(cssText) {
|
|
102
|
+
const aliases = new Map();
|
|
103
|
+
const lines = cssText.split('\n');
|
|
104
|
+
const cleanLines = [];
|
|
105
|
+
|
|
106
|
+
for (let line of lines) {
|
|
107
|
+
const trimmed = line.trim();
|
|
108
|
+
|
|
109
|
+
if (trimmed.startsWith('@alias')) {
|
|
110
|
+
const aliasMatch = trimmed.match(/^@alias\s+([a-zA-Z0-9_-]+)\s+(.+?)\s*;$/);
|
|
111
|
+
if (aliasMatch) {
|
|
112
|
+
const aliasName = aliasMatch[1];
|
|
113
|
+
const originalProperty = aliasMatch[2];
|
|
114
|
+
|
|
115
|
+
aliases.set(aliasName, originalProperty);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
cleanLines.push(line);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
aliases,
|
|
125
|
+
css: cleanLines.join('\n')
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 解析 @macro 语句
|
|
131
|
+
*/
|
|
132
|
+
function parseMacroStatements(cssText, config) {
|
|
133
|
+
if (!config.enableMacros) {
|
|
134
|
+
return { macros: new Map(), css: cssText };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const macros = new Map();
|
|
138
|
+
const lines = cssText.split('\n');
|
|
139
|
+
const cleanLines = [];
|
|
140
|
+
let inMacroDefinition = false;
|
|
141
|
+
let currentMacroName = '';
|
|
142
|
+
let currentMacroParams = [];
|
|
143
|
+
let currentMacroBody = [];
|
|
144
|
+
let braceDepth = 0;
|
|
145
|
+
|
|
146
|
+
for (let line of lines) {
|
|
147
|
+
const trimmed = line.trim();
|
|
148
|
+
|
|
149
|
+
if (!inMacroDefinition && trimmed.startsWith('@macro')) {
|
|
150
|
+
const macroMatch = trimmed.match(/^@macro\s+([a-zA-Z0-9_-]+)\s*\(([^)]*)\)\s*\{$/);
|
|
151
|
+
if (macroMatch) {
|
|
152
|
+
inMacroDefinition = true;
|
|
153
|
+
currentMacroName = macroMatch[1];
|
|
154
|
+
currentMacroParams = macroMatch[2]
|
|
155
|
+
.split(',')
|
|
156
|
+
.map(param => param.trim())
|
|
157
|
+
.filter(param => param)
|
|
158
|
+
.map(param => {
|
|
159
|
+
const paramParts = param.split(':').map(p => p.trim());
|
|
160
|
+
return {
|
|
161
|
+
name: paramParts[0].slice(1),
|
|
162
|
+
defaultValue: paramParts[1] || null
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
currentMacroBody = [];
|
|
166
|
+
braceDepth = 1;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (inMacroDefinition) {
|
|
172
|
+
for (const char of line) {
|
|
173
|
+
if (char === '{') braceDepth++;
|
|
174
|
+
if (char === '}') braceDepth--;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (braceDepth === 0) {
|
|
178
|
+
macros.set(currentMacroName, {
|
|
179
|
+
params: currentMacroParams,
|
|
180
|
+
body: currentMacroBody.join('\n')
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
inMacroDefinition = false;
|
|
184
|
+
currentMacroName = '';
|
|
185
|
+
currentMacroParams = [];
|
|
186
|
+
currentMacroBody = [];
|
|
187
|
+
} else {
|
|
188
|
+
currentMacroBody.push(line);
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
cleanLines.push(line);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
macros,
|
|
198
|
+
css: cleanLines.join('\n')
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 应用宏调用
|
|
204
|
+
*/
|
|
205
|
+
function applyMacroCalls(cssText, macros, config) {
|
|
206
|
+
if (!config.enableMacros || macros.size === 0) {
|
|
207
|
+
return cssText;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const macroNames = Array.from(macros.keys()).map(name =>
|
|
211
|
+
name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
212
|
+
).join('|');
|
|
213
|
+
|
|
214
|
+
const macroCallRegex = new RegExp(`@(${macroNames})\\s*\\(([^)]*)\\)\\s*;?`, 'g');
|
|
215
|
+
|
|
216
|
+
const processMacroCalls = (text) => {
|
|
217
|
+
let result = text;
|
|
218
|
+
let changed = false;
|
|
219
|
+
|
|
220
|
+
do {
|
|
221
|
+
changed = false;
|
|
222
|
+
result = result.replace(macroCallRegex, (match, macroName, argsStr) => {
|
|
223
|
+
changed = true;
|
|
224
|
+
|
|
225
|
+
const macro = macros.get(macroName);
|
|
226
|
+
if (!macro) {
|
|
227
|
+
console.warn(`未定义的宏: ${macroName}`);
|
|
228
|
+
return match;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const args = parseMacroArguments(argsStr, macro.params);
|
|
232
|
+
return applyMacro(macro.body, args, config);
|
|
233
|
+
});
|
|
234
|
+
} while (changed);
|
|
235
|
+
|
|
236
|
+
return result;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return processMacroCalls(cssText);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 解析宏参数
|
|
244
|
+
*/
|
|
245
|
+
function parseMacroArguments(argsStr, paramDefs) {
|
|
246
|
+
const argsMap = new Map();
|
|
247
|
+
const argValues = parseArgumentList(argsStr);
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < paramDefs.length; i++) {
|
|
250
|
+
const param = paramDefs[i];
|
|
251
|
+
let value;
|
|
252
|
+
|
|
253
|
+
if (i < argValues.length) {
|
|
254
|
+
value = argValues[i];
|
|
255
|
+
} else if (param.defaultValue !== null) {
|
|
256
|
+
value = param.defaultValue;
|
|
257
|
+
} else {
|
|
258
|
+
console.warn(`宏调用缺少必需参数: ${param.name}`);
|
|
259
|
+
value = '';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
argsMap.set(param.name, value);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (argValues.length > paramDefs.length) {
|
|
266
|
+
console.warn(`宏调用传递了过多参数,多余的参数将被忽略`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return argsMap;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 解析参数列表
|
|
274
|
+
*/
|
|
275
|
+
function parseArgumentList(argsStr) {
|
|
276
|
+
const args = [];
|
|
277
|
+
let currentArg = '';
|
|
278
|
+
let inQuotes = false;
|
|
279
|
+
let quoteChar = '';
|
|
280
|
+
let parenDepth = 0;
|
|
281
|
+
let bracketDepth = 0;
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < argsStr.length; i++) {
|
|
284
|
+
const char = argsStr[i];
|
|
285
|
+
|
|
286
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
287
|
+
inQuotes = true;
|
|
288
|
+
quoteChar = char;
|
|
289
|
+
} else if (char === quoteChar && inQuotes) {
|
|
290
|
+
inQuotes = false;
|
|
291
|
+
quoteChar = '';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!inQuotes) {
|
|
295
|
+
if (char === '(') parenDepth++;
|
|
296
|
+
if (char === ')') parenDepth--;
|
|
297
|
+
if (char === '[') bracketDepth++;
|
|
298
|
+
if (char === ']') bracketDepth--;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (char === ',' && !inQuotes && parenDepth === 0 && bracketDepth === 0) {
|
|
302
|
+
args.push(currentArg.trim());
|
|
303
|
+
currentArg = '';
|
|
304
|
+
} else {
|
|
305
|
+
currentArg += char;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (currentArg.trim()) {
|
|
310
|
+
args.push(currentArg.trim());
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return args;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 应用宏替换
|
|
318
|
+
*/
|
|
319
|
+
function applyMacro(macroBody, args, config) {
|
|
320
|
+
let result = macroBody;
|
|
321
|
+
|
|
322
|
+
for (const [paramName, paramValue] of args) {
|
|
323
|
+
const paramRegex = new RegExp(`\\$${paramName}`, 'g');
|
|
324
|
+
result = result.replace(paramRegex, paramValue);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (config.enableMacros) {
|
|
328
|
+
const macroNames = Array.from(macroRegistry.keys()).map(name =>
|
|
329
|
+
name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
330
|
+
).join('|');
|
|
331
|
+
|
|
332
|
+
if (macroNames) {
|
|
333
|
+
const macroCallRegex = new RegExp(`@(${macroNames})\\s*\\(([^)]*)\\)\\s*;?`, 'g');
|
|
334
|
+
|
|
335
|
+
let changed;
|
|
336
|
+
do {
|
|
337
|
+
changed = false;
|
|
338
|
+
result = result.replace(macroCallRegex, (match, macroName, argsStr) => {
|
|
339
|
+
changed = true;
|
|
340
|
+
|
|
341
|
+
const macro = macroRegistry.get(macroName);
|
|
342
|
+
if (!macro) return match;
|
|
343
|
+
|
|
344
|
+
const nestedArgs = parseMacroArguments(argsStr, macro.params);
|
|
345
|
+
return applyMacro(macro.body, nestedArgs, config);
|
|
346
|
+
});
|
|
347
|
+
} while (changed);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
result = processCSSValue(result, config);
|
|
352
|
+
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
|
|
95
356
|
/**
|
|
96
357
|
* 将连字符分隔的字符串转换为驼峰命名
|
|
97
|
-
* @param {string} str - 输入字符串
|
|
98
|
-
* @returns {string} 驼峰命名字符串
|
|
99
358
|
*/
|
|
100
359
|
function camelCase(str) {
|
|
101
360
|
return str.replace(/-([a-z])/g, function(match, letter) {
|
|
@@ -105,7 +364,6 @@ const styimat = (function() {
|
|
|
105
364
|
|
|
106
365
|
/**
|
|
107
366
|
* 检测浏览器是否支持 Display P3
|
|
108
|
-
* @returns {boolean} 是否支持P3
|
|
109
367
|
*/
|
|
110
368
|
function detectP3Support() {
|
|
111
369
|
if (p3Supported !== null) return p3Supported;
|
|
@@ -126,36 +384,28 @@ const styimat = (function() {
|
|
|
126
384
|
}
|
|
127
385
|
|
|
128
386
|
/**
|
|
129
|
-
* 解析一行CSS
|
|
130
|
-
* 返回格式:[{属性名:值}, {属性名:值}, ...]
|
|
131
|
-
* @param {string} cssString - 要解析的CSS字符串
|
|
132
|
-
* @returns {Array} 包含属性名值对象的数组
|
|
387
|
+
* 解析一行CSS
|
|
133
388
|
*/
|
|
134
|
-
function parseSingleLineCSS(cssString) {
|
|
135
|
-
// 移除首尾空白字符
|
|
389
|
+
function parseSingleLineCSS(cssString, aliases=[]) {
|
|
136
390
|
let str = cssString.trim();
|
|
137
391
|
|
|
138
|
-
// 如果以分号结尾,移除最后一个分号
|
|
139
392
|
if (str.endsWith(';')) {
|
|
140
393
|
str = str.slice(0, -1);
|
|
141
394
|
}
|
|
142
395
|
|
|
143
|
-
// 如果字符串为空,返回空数组
|
|
144
396
|
if (!str) {
|
|
145
397
|
return [];
|
|
146
398
|
}
|
|
147
399
|
|
|
148
|
-
// 分割多个CSS声明(以分号分隔)
|
|
149
400
|
const declarations = [];
|
|
150
401
|
let currentDeclaration = '';
|
|
151
|
-
let inParens = 0;
|
|
152
|
-
let inQuotes = false;
|
|
153
|
-
let quoteChar = '';
|
|
402
|
+
let inParens = 0;
|
|
403
|
+
let inQuotes = false;
|
|
404
|
+
let quoteChar = '';
|
|
154
405
|
|
|
155
406
|
for (let i = 0; i < str.length; i++) {
|
|
156
407
|
const char = str[i];
|
|
157
408
|
|
|
158
|
-
// 处理引号
|
|
159
409
|
if ((char === '"' || char === "'") && !inQuotes) {
|
|
160
410
|
inQuotes = true;
|
|
161
411
|
quoteChar = char;
|
|
@@ -164,7 +414,6 @@ const styimat = (function() {
|
|
|
164
414
|
quoteChar = '';
|
|
165
415
|
}
|
|
166
416
|
|
|
167
|
-
// 处理括号(不在引号内时)
|
|
168
417
|
if (!inQuotes) {
|
|
169
418
|
if (char === '(') {
|
|
170
419
|
inParens++;
|
|
@@ -173,7 +422,6 @@ const styimat = (function() {
|
|
|
173
422
|
}
|
|
174
423
|
}
|
|
175
424
|
|
|
176
|
-
// 如果遇到分号且不在括号和引号内,则分割声明
|
|
177
425
|
if (char === ';' && !inQuotes && inParens === 0) {
|
|
178
426
|
if (currentDeclaration.trim()) {
|
|
179
427
|
declarations.push(currentDeclaration.trim());
|
|
@@ -184,19 +432,15 @@ const styimat = (function() {
|
|
|
184
432
|
}
|
|
185
433
|
}
|
|
186
434
|
|
|
187
|
-
// 添加最后一个声明(如果没有分号结尾)
|
|
188
435
|
if (currentDeclaration.trim()) {
|
|
189
436
|
declarations.push(currentDeclaration.trim());
|
|
190
437
|
}
|
|
191
438
|
|
|
192
|
-
// 解析每个声明为对象
|
|
193
439
|
const result = [];
|
|
194
440
|
|
|
195
441
|
for (const declaration of declarations) {
|
|
196
|
-
// 跳过空声明
|
|
197
442
|
if (!declaration.trim()) continue;
|
|
198
443
|
|
|
199
|
-
// 查找第一个冒号的位置
|
|
200
444
|
const colonIndex = findFirstColonOutsideQuotes(declaration);
|
|
201
445
|
|
|
202
446
|
if (colonIndex === -1) {
|
|
@@ -204,16 +448,17 @@ const styimat = (function() {
|
|
|
204
448
|
continue;
|
|
205
449
|
}
|
|
206
450
|
|
|
207
|
-
|
|
208
|
-
const property = declaration.substring(0, colonIndex).trim();
|
|
451
|
+
let property = declaration.substring(0, colonIndex).trim();
|
|
209
452
|
let value = declaration.substring(colonIndex + 1).trim();
|
|
210
453
|
|
|
211
|
-
// 如果值以分号结尾,移除它(理论上不应该有,但处理一下)
|
|
212
454
|
if (value.endsWith(';')) {
|
|
213
455
|
value = value.slice(0, -1).trim();
|
|
214
456
|
}
|
|
215
457
|
|
|
216
|
-
|
|
458
|
+
if (aliases.get(property)) {
|
|
459
|
+
property = aliases.get(property);
|
|
460
|
+
}
|
|
461
|
+
|
|
217
462
|
result.push({ [property]: value });
|
|
218
463
|
}
|
|
219
464
|
|
|
@@ -222,8 +467,6 @@ const styimat = (function() {
|
|
|
222
467
|
|
|
223
468
|
/**
|
|
224
469
|
* 查找不在引号内的第一个冒号位置
|
|
225
|
-
* @param {string} str
|
|
226
|
-
* @returns {number}
|
|
227
470
|
*/
|
|
228
471
|
function findFirstColonOutsideQuotes(str) {
|
|
229
472
|
let inQuotes = false;
|
|
@@ -232,7 +475,6 @@ const styimat = (function() {
|
|
|
232
475
|
for (let i = 0; i < str.length; i++) {
|
|
233
476
|
const char = str[i];
|
|
234
477
|
|
|
235
|
-
// 处理引号
|
|
236
478
|
if ((char === '"' || char === "'") && !inQuotes) {
|
|
237
479
|
inQuotes = true;
|
|
238
480
|
quoteChar = char;
|
|
@@ -241,7 +483,6 @@ const styimat = (function() {
|
|
|
241
483
|
quoteChar = '';
|
|
242
484
|
}
|
|
243
485
|
|
|
244
|
-
// 如果找到冒号且不在引号内,返回位置
|
|
245
486
|
if (char === ':' && !inQuotes) {
|
|
246
487
|
return i;
|
|
247
488
|
}
|
|
@@ -252,29 +493,19 @@ const styimat = (function() {
|
|
|
252
493
|
|
|
253
494
|
/**
|
|
254
495
|
* 增强的数学表达式解析和计算
|
|
255
|
-
* @param {string} expression - 数学表达式
|
|
256
|
-
* @param {Object} config - 配置对象
|
|
257
|
-
* @returns {string} 计算结果
|
|
258
496
|
*/
|
|
259
497
|
function evaluateMathExpression(expression, config) {
|
|
260
|
-
// 如果禁用math()增强,则返回原始表达式
|
|
261
498
|
if (!config.enableMath) {
|
|
262
499
|
return `math(${expression})`;
|
|
263
500
|
}
|
|
264
501
|
|
|
265
502
|
try {
|
|
266
|
-
// 清理表达式:移除空白字符
|
|
267
503
|
let cleanExpr = expression.replace(/\s+/g, '');
|
|
268
|
-
|
|
269
|
-
// 解析数学表达式
|
|
270
504
|
const result = parseMathExpression(cleanExpr, config);
|
|
271
505
|
|
|
272
|
-
// 如果结果包含单位,保留原始格式
|
|
273
506
|
if (hasUnits(cleanExpr)) {
|
|
274
|
-
// 处理带单位的表达式
|
|
275
507
|
return processUnitExpression(cleanExpr, result);
|
|
276
508
|
} else {
|
|
277
|
-
// 纯数字表达式,直接计算结果
|
|
278
509
|
return roundNumber(result, config.mathPrecision);
|
|
279
510
|
}
|
|
280
511
|
} catch (error) {
|
|
@@ -285,12 +516,8 @@ const styimat = (function() {
|
|
|
285
516
|
|
|
286
517
|
/**
|
|
287
518
|
* 解析数学表达式
|
|
288
|
-
* @param {string} expr - 清理后的表达式
|
|
289
|
-
* @param {Object} config - 配置对象
|
|
290
|
-
* @returns {number} 计算结果
|
|
291
519
|
*/
|
|
292
520
|
function parseMathExpression(expr, config) {
|
|
293
|
-
// 处理括号
|
|
294
521
|
while (expr.includes('(') && expr.includes(')')) {
|
|
295
522
|
const start = expr.lastIndexOf('(');
|
|
296
523
|
const end = expr.indexOf(')', start);
|
|
@@ -303,18 +530,13 @@ const styimat = (function() {
|
|
|
303
530
|
expr = expr.substring(0, start) + innerResult + expr.substring(end + 1);
|
|
304
531
|
}
|
|
305
532
|
|
|
306
|
-
// 现在expr应该没有括号了
|
|
307
533
|
return evaluateSimpleExpression(expr, config);
|
|
308
534
|
}
|
|
309
535
|
|
|
310
536
|
/**
|
|
311
537
|
* 评估简单表达式(无括号)
|
|
312
|
-
* @param {string} expr - 简单表达式
|
|
313
|
-
* @param {Object} config - 配置对象
|
|
314
|
-
* @returns {number} 计算结果
|
|
315
538
|
*/
|
|
316
539
|
function evaluateSimpleExpression(expr, config) {
|
|
317
|
-
// 处理操作符优先级:乘除优先于加减
|
|
318
540
|
const operators = [
|
|
319
541
|
{ regex: /([\d.]+(?:[a-zA-Z%]+)?)\*([\d.]+(?:[a-zA-Z%]+)?)/, handler: multiply },
|
|
320
542
|
{ regex: /([\d.]+(?:[a-zA-Z%]+)?)\/([\d.]+(?:[a-zA-Z%]+)?)/, handler: divide },
|
|
@@ -324,7 +546,6 @@ const styimat = (function() {
|
|
|
324
546
|
|
|
325
547
|
let lastExpr = expr;
|
|
326
548
|
|
|
327
|
-
// 按照优先级处理操作符
|
|
328
549
|
for (const op of operators) {
|
|
329
550
|
let match;
|
|
330
551
|
while ((match = expr.match(op.regex)) !== null) {
|
|
@@ -332,30 +553,24 @@ const styimat = (function() {
|
|
|
332
553
|
const right = parseValueWithUnit(match[2]);
|
|
333
554
|
const result = op.handler(left, right);
|
|
334
555
|
|
|
335
|
-
// 替换匹配的部分
|
|
336
556
|
expr = expr.substring(0, match.index) +
|
|
337
557
|
result.value +
|
|
338
558
|
expr.substring(match.index + match[0].length);
|
|
339
559
|
}
|
|
340
560
|
}
|
|
341
561
|
|
|
342
|
-
// 如果表达式无法进一步简化,尝试解析为数字
|
|
343
562
|
if (expr !== lastExpr) {
|
|
344
563
|
return evaluateSimpleExpression(expr, config);
|
|
345
564
|
}
|
|
346
565
|
|
|
347
|
-
// 解析最终结果
|
|
348
566
|
const parsed = parseValueWithUnit(expr);
|
|
349
567
|
return parsed.value;
|
|
350
568
|
}
|
|
351
569
|
|
|
352
570
|
/**
|
|
353
571
|
* 解析带单位的数值
|
|
354
|
-
* @param {string} str - 字符串值
|
|
355
|
-
* @returns {Object} {value: number, unit: string}
|
|
356
572
|
*/
|
|
357
573
|
function parseValueWithUnit(str) {
|
|
358
|
-
// 匹配数值和单位
|
|
359
574
|
const match = str.match(/^([\d.]+)([a-zA-Z%]*)$/);
|
|
360
575
|
|
|
361
576
|
if (!match) {
|
|
@@ -370,8 +585,6 @@ const styimat = (function() {
|
|
|
370
585
|
|
|
371
586
|
/**
|
|
372
587
|
* 检查表达式是否包含单位
|
|
373
|
-
* @param {string} expr - 表达式
|
|
374
|
-
* @returns {boolean} 是否包含单位
|
|
375
588
|
*/
|
|
376
589
|
function hasUnits(expr) {
|
|
377
590
|
return /[a-zA-Z%]/.test(expr);
|
|
@@ -379,24 +592,18 @@ const styimat = (function() {
|
|
|
379
592
|
|
|
380
593
|
/**
|
|
381
594
|
* 处理带单位的表达式
|
|
382
|
-
* @param {string} originalExpr - 原始表达式
|
|
383
|
-
* @param {number} result - 计算结果
|
|
384
|
-
* @returns {string} 处理后的表达式
|
|
385
595
|
*/
|
|
386
596
|
function processUnitExpression(originalExpr, result) {
|
|
387
|
-
// 提取原始表达式中的单位
|
|
388
597
|
const unitMatch = originalExpr.match(/([a-zA-Z%]+)(?!.*[a-zA-Z%])/);
|
|
389
598
|
|
|
390
599
|
if (unitMatch) {
|
|
391
600
|
return result + unitMatch[1];
|
|
392
601
|
}
|
|
393
602
|
|
|
394
|
-
// 如果没有明确单位,检查是否为百分比
|
|
395
603
|
if (originalExpr.includes('%')) {
|
|
396
604
|
return result + '%';
|
|
397
605
|
}
|
|
398
606
|
|
|
399
|
-
// 默认为像素
|
|
400
607
|
return result + 'px';
|
|
401
608
|
}
|
|
402
609
|
|
|
@@ -406,7 +613,6 @@ const styimat = (function() {
|
|
|
406
613
|
return { value: a.value * b.value, unit: a.unit || b.unit };
|
|
407
614
|
}
|
|
408
615
|
|
|
409
|
-
// 单位不匹配,返回原始表达式
|
|
410
616
|
return { value: `${a.value}${a.unit}*${b.value}${b.unit}`, unit: '' };
|
|
411
617
|
}
|
|
412
618
|
|
|
@@ -416,14 +622,11 @@ const styimat = (function() {
|
|
|
416
622
|
}
|
|
417
623
|
|
|
418
624
|
if (a.unit && !b.unit) {
|
|
419
|
-
// 如 100px / 2
|
|
420
625
|
return { value: a.value / b.value, unit: a.unit };
|
|
421
626
|
} else if (a.unit === b.unit) {
|
|
422
|
-
// 如 100px / 2px
|
|
423
627
|
return { value: a.value / b.value, unit: '' };
|
|
424
628
|
}
|
|
425
629
|
|
|
426
|
-
// 单位不匹配,返回原始表达式
|
|
427
630
|
return { value: `${a.value}${a.unit}/${b.value}${b.unit}`, unit: '' };
|
|
428
631
|
}
|
|
429
632
|
|
|
@@ -432,7 +635,6 @@ const styimat = (function() {
|
|
|
432
635
|
return { value: a.value + b.value, unit: a.unit };
|
|
433
636
|
}
|
|
434
637
|
|
|
435
|
-
// 单位不匹配,返回原始表达式
|
|
436
638
|
return { value: `${a.value}${a.unit}+${b.value}${b.unit}`, unit: '' };
|
|
437
639
|
}
|
|
438
640
|
|
|
@@ -441,21 +643,16 @@ const styimat = (function() {
|
|
|
441
643
|
return { value: a.value - b.value, unit: a.unit };
|
|
442
644
|
}
|
|
443
645
|
|
|
444
|
-
// 单位不匹配,返回原始表达式
|
|
445
646
|
return { value: `${a.value}${a.unit}-${b.value}${b.unit}`, unit: '' };
|
|
446
647
|
}
|
|
447
648
|
|
|
448
649
|
/**
|
|
449
650
|
* 四舍五入到指定精度
|
|
450
|
-
* @param {number} num - 要四舍五入的数字
|
|
451
|
-
* @param {number} precision - 精度
|
|
452
|
-
* @returns {string} 四舍五入后的数字字符串
|
|
453
651
|
*/
|
|
454
652
|
function roundNumber(num, precision = 6) {
|
|
455
653
|
const factor = Math.pow(10, precision);
|
|
456
654
|
const rounded = Math.round(num * factor) / factor;
|
|
457
655
|
|
|
458
|
-
// 移除不必要的尾随零
|
|
459
656
|
const str = rounded.toString();
|
|
460
657
|
if (str.includes('.')) {
|
|
461
658
|
return str.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
|
|
@@ -465,9 +662,6 @@ const styimat = (function() {
|
|
|
465
662
|
|
|
466
663
|
/**
|
|
467
664
|
* 处理所有的math()函数
|
|
468
|
-
* @param {string} cssValue - CSS属性值
|
|
469
|
-
* @param {Object} config - 配置对象
|
|
470
|
-
* @returns {string} 转换后的CSS值
|
|
471
665
|
*/
|
|
472
666
|
function processMathFunctions(cssValue, config) {
|
|
473
667
|
if (!config.enableMath) {
|
|
@@ -475,18 +669,14 @@ const styimat = (function() {
|
|
|
475
669
|
}
|
|
476
670
|
|
|
477
671
|
let result = cssValue;
|
|
478
|
-
|
|
479
|
-
// 匹配math()函数
|
|
480
672
|
const mathRegex = /math\(([^)]+)\)/gi;
|
|
481
673
|
|
|
482
|
-
// 递归处理嵌套的math()函数
|
|
483
674
|
const processMath = (str) => {
|
|
484
675
|
return str.replace(mathRegex, (match, expression) => {
|
|
485
676
|
return evaluateMathExpression(expression, config);
|
|
486
677
|
});
|
|
487
678
|
};
|
|
488
679
|
|
|
489
|
-
// 递归处理,直到没有更多math()函数
|
|
490
680
|
let lastResult;
|
|
491
681
|
do {
|
|
492
682
|
lastResult = result;
|
|
@@ -497,10 +687,7 @@ const styimat = (function() {
|
|
|
497
687
|
}
|
|
498
688
|
|
|
499
689
|
/**
|
|
500
|
-
* 解析并转换所有的 Lab 和 LCH
|
|
501
|
-
* @param {string} cssValue - CSS属性值
|
|
502
|
-
* @param {Object} config - 配置对象
|
|
503
|
-
* @returns {string} 转换后的CSS值
|
|
690
|
+
* 解析并转换所有的 Lab 和 LCH 颜色
|
|
504
691
|
*/
|
|
505
692
|
function convertAllLabLchColors(cssValue, config) {
|
|
506
693
|
if (!config.convertLabToRGB && !config.convertLchToRGB) {
|
|
@@ -508,27 +695,19 @@ const styimat = (function() {
|
|
|
508
695
|
}
|
|
509
696
|
|
|
510
697
|
let result = cssValue;
|
|
511
|
-
|
|
512
|
-
// 首先处理特殊的十六进制格式
|
|
513
698
|
result = parseHexColorFormats(result, config);
|
|
514
699
|
|
|
515
|
-
// 一次性处理所有的 lab() 和 lch() 函数
|
|
516
700
|
const colorFunctionRegex = /(lab|lch)\([^)]+\)/gi;
|
|
517
|
-
|
|
518
|
-
// 存储已经处理过的颜色字符串,避免重复处理
|
|
519
701
|
const processedColors = new Map();
|
|
520
702
|
|
|
521
|
-
// 使用一个函数来递归处理嵌套的颜色函数
|
|
522
703
|
const processColorFunctions = (str) => {
|
|
523
704
|
return str.replace(colorFunctionRegex, (match) => {
|
|
524
|
-
// 如果这个颜色已经处理过,直接返回缓存结果
|
|
525
705
|
if (processedColors.has(match)) {
|
|
526
706
|
return processedColors.get(match);
|
|
527
707
|
}
|
|
528
708
|
|
|
529
709
|
let converted = match;
|
|
530
710
|
|
|
531
|
-
// 根据函数类型处理
|
|
532
711
|
if (match.toLowerCase().startsWith('lab(')) {
|
|
533
712
|
if (config.convertLabToRGB) {
|
|
534
713
|
converted = convertSingleLabColor(match, config);
|
|
@@ -539,13 +718,11 @@ const styimat = (function() {
|
|
|
539
718
|
}
|
|
540
719
|
}
|
|
541
720
|
|
|
542
|
-
// 缓存结果
|
|
543
721
|
processedColors.set(match, converted);
|
|
544
722
|
return converted;
|
|
545
723
|
});
|
|
546
724
|
};
|
|
547
725
|
|
|
548
|
-
// 递归处理,直到没有更多颜色函数
|
|
549
726
|
let lastResult;
|
|
550
727
|
do {
|
|
551
728
|
lastResult = result;
|
|
@@ -556,32 +733,25 @@ const styimat = (function() {
|
|
|
556
733
|
}
|
|
557
734
|
|
|
558
735
|
/**
|
|
559
|
-
*
|
|
560
|
-
* @param {string} value - 颜色值
|
|
561
|
-
* @param {Object} config - 配置对象
|
|
562
|
-
* @returns {string} 转换后的CSS颜色
|
|
736
|
+
* 解析十六进制颜色格式
|
|
563
737
|
*/
|
|
564
738
|
function parseHexColorFormats(value, config) {
|
|
565
739
|
let result = value;
|
|
566
740
|
|
|
567
|
-
// 使用正则表达式一次性匹配所有 lab# 和 lch# 格式
|
|
568
741
|
const hexLabRegex = /lab#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi;
|
|
569
742
|
const hexLchRegex = /lch#([0-9a-f]{2})([0-9a-f]{2})(\d{1,3})/gi;
|
|
570
743
|
|
|
571
|
-
// 存储处理过的十六进制颜色,避免重复处理
|
|
572
744
|
const processedHexColors = new Map();
|
|
573
745
|
|
|
574
|
-
// 处理 lab# 格式
|
|
575
746
|
result = result.replace(hexLabRegex, (match, L_hex, A_hex, B_hex) => {
|
|
576
747
|
if (processedHexColors.has(match)) {
|
|
577
748
|
return processedHexColors.get(match);
|
|
578
749
|
}
|
|
579
750
|
|
|
580
751
|
try {
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
const
|
|
584
|
-
const B = (parseInt(B_hex, 16) - 128) * 1.5; // 大约 -192 到 192
|
|
752
|
+
const L = parseInt(L_hex, 16) / 255 * 100;
|
|
753
|
+
const A = (parseInt(A_hex, 16) - 128) * 1.5;
|
|
754
|
+
const B = (parseInt(B_hex, 16) - 128) * 1.5;
|
|
585
755
|
|
|
586
756
|
const converted = generateColorString(L, A, B, config);
|
|
587
757
|
processedHexColors.set(match, converted);
|
|
@@ -592,17 +762,15 @@ const styimat = (function() {
|
|
|
592
762
|
}
|
|
593
763
|
});
|
|
594
764
|
|
|
595
|
-
// 处理 lch# 格式
|
|
596
765
|
result = result.replace(hexLchRegex, (match, L_hex, C_hex, H_dec) => {
|
|
597
766
|
if (processedHexColors.has(match)) {
|
|
598
767
|
return processedHexColors.get(match);
|
|
599
768
|
}
|
|
600
769
|
|
|
601
770
|
try {
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
const H = parseInt(H_dec) / 100 * 360; // 0-360
|
|
771
|
+
const L = parseInt(L_hex, 16) / 255 * 100;
|
|
772
|
+
const C = parseInt(C_hex, 16) / 255 * 150;
|
|
773
|
+
const H = parseInt(H_dec) / 100 * 360;
|
|
606
774
|
|
|
607
775
|
const lab = lchToLab(L, C, H);
|
|
608
776
|
const converted = generateColorString(lab.L, lab.a, lab.b, config);
|
|
@@ -619,12 +787,8 @@ const styimat = (function() {
|
|
|
619
787
|
|
|
620
788
|
/**
|
|
621
789
|
* 转换单个 lab() 颜色函数
|
|
622
|
-
* @param {string} labString - lab() 颜色字符串
|
|
623
|
-
* @param {Object} config - 配置对象
|
|
624
|
-
* @returns {string} 转换后的颜色字符串
|
|
625
790
|
*/
|
|
626
791
|
function convertSingleLabColor(labString, config) {
|
|
627
|
-
// 使用更精确的正则匹配 lab() 函数
|
|
628
792
|
const labRegex = /lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i;
|
|
629
793
|
const match = labString.match(labRegex);
|
|
630
794
|
|
|
@@ -633,14 +797,11 @@ const styimat = (function() {
|
|
|
633
797
|
}
|
|
634
798
|
|
|
635
799
|
try {
|
|
636
|
-
// 转换为数字
|
|
637
800
|
let L = parseFloat(match[1]);
|
|
638
801
|
|
|
639
|
-
// 如果L有百分号,转换为0-100范围的值
|
|
640
802
|
if (match[2] === '%') {
|
|
641
|
-
L = L;
|
|
803
|
+
L = L;
|
|
642
804
|
} else {
|
|
643
|
-
// 没有百分号,确保在0-100范围内
|
|
644
805
|
if (L < 0) L = 0;
|
|
645
806
|
if (L > 100) L = 100;
|
|
646
807
|
}
|
|
@@ -648,7 +809,6 @@ const styimat = (function() {
|
|
|
648
809
|
const A = parseFloat(match[3]);
|
|
649
810
|
const B = parseFloat(match[4]);
|
|
650
811
|
|
|
651
|
-
// 处理 alpha 通道(如果有)
|
|
652
812
|
const alphaValue = match[5] !== undefined
|
|
653
813
|
? match[5].includes('%')
|
|
654
814
|
? parseFloat(match[5]) / 100
|
|
@@ -664,12 +824,8 @@ const styimat = (function() {
|
|
|
664
824
|
|
|
665
825
|
/**
|
|
666
826
|
* 转换单个 lch() 颜色函数
|
|
667
|
-
* @param {string} lchString - lch() 颜色字符串
|
|
668
|
-
* @param {Object} config - 配置对象
|
|
669
|
-
* @returns {string} 转换后的颜色字符串
|
|
670
827
|
*/
|
|
671
828
|
function convertSingleLchColor(lchString, config) {
|
|
672
|
-
// 使用更精确的正则匹配 lch() 函数
|
|
673
829
|
const lchRegex = /lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i;
|
|
674
830
|
const match = lchString.match(lchRegex);
|
|
675
831
|
|
|
@@ -678,14 +834,11 @@ const styimat = (function() {
|
|
|
678
834
|
}
|
|
679
835
|
|
|
680
836
|
try {
|
|
681
|
-
// 转换为数字
|
|
682
837
|
let L = parseFloat(match[1]);
|
|
683
838
|
|
|
684
|
-
// 如果L有百分号,转换为0-100范围的值
|
|
685
839
|
if (match[2] === '%') {
|
|
686
|
-
L = L;
|
|
840
|
+
L = L;
|
|
687
841
|
} else {
|
|
688
|
-
// 没有百分号,确保在0-100范围内
|
|
689
842
|
if (L < 0) L = 0;
|
|
690
843
|
if (L > 100) L = 100;
|
|
691
844
|
}
|
|
@@ -693,18 +846,14 @@ const styimat = (function() {
|
|
|
693
846
|
const C = parseFloat(match[3]);
|
|
694
847
|
let H = parseFloat(match[4]);
|
|
695
848
|
|
|
696
|
-
// 验证C范围:>= 0
|
|
697
849
|
if (C < 0) {
|
|
698
850
|
console.warn(`LCH中的C值不能为负: ${C}`);
|
|
699
851
|
}
|
|
700
852
|
|
|
701
|
-
|
|
702
|
-
H = ((H % 360) + 360) % 360; // 标准化到0-360
|
|
853
|
+
H = ((H % 360) + 360) % 360;
|
|
703
854
|
|
|
704
|
-
// LCH -> Lab
|
|
705
855
|
const lab = lchToLab(L, C, H);
|
|
706
856
|
|
|
707
|
-
// 处理 alpha 通道(如果有)
|
|
708
857
|
const alphaValue = match[6] !== undefined
|
|
709
858
|
? match[6].includes('%')
|
|
710
859
|
? parseFloat(match[6]) / 100
|
|
@@ -719,28 +868,21 @@ const styimat = (function() {
|
|
|
719
868
|
}
|
|
720
869
|
|
|
721
870
|
/**
|
|
722
|
-
* LCH 转换为 Lab
|
|
723
|
-
* @param {number} L - 明度 0-100 或 0%-100%
|
|
724
|
-
* @param {number} C - 色度 >=0
|
|
725
|
-
* @param {number} H - 色相角 0-360 度
|
|
726
|
-
* @returns {Object} Lab 值 {L, a, b}
|
|
871
|
+
* LCH 转换为 Lab
|
|
727
872
|
*/
|
|
728
873
|
function lchToLab(L, C, H) {
|
|
729
|
-
// 角度转换为弧度
|
|
730
874
|
const H_rad = H * Math.PI / 180;
|
|
731
|
-
|
|
732
|
-
// LCH -> Lab 转换公式
|
|
733
875
|
const a = C * Math.cos(H_rad);
|
|
734
876
|
const b = C * Math.sin(H_rad);
|
|
735
877
|
|
|
736
878
|
return { L, a, b };
|
|
737
879
|
}
|
|
738
880
|
|
|
739
|
-
|
|
881
|
+
/**
|
|
882
|
+
* Lab -> sRGB 转换函数
|
|
883
|
+
*/
|
|
740
884
|
function preciseLabToRGB(L, a, b) {
|
|
741
|
-
// 1. Lab -> XYZ (D65白点)
|
|
742
885
|
const labToXyz = (L, a, b) => {
|
|
743
|
-
// D65 白点参考值
|
|
744
886
|
const refX = 0.95047;
|
|
745
887
|
const refY = 1.00000;
|
|
746
888
|
const refZ = 1.08883;
|
|
@@ -763,9 +905,7 @@ const styimat = (function() {
|
|
|
763
905
|
];
|
|
764
906
|
};
|
|
765
907
|
|
|
766
|
-
// 2. XYZ -> Linear RGB
|
|
767
908
|
const xyzToLinearRgb = (x, y, z) => {
|
|
768
|
-
// sRGB 转换矩阵 (D65)
|
|
769
909
|
const M = [
|
|
770
910
|
[ 3.2404542, -1.5371385, -0.4985314],
|
|
771
911
|
[-0.9692660, 1.8760108, 0.0415560],
|
|
@@ -779,7 +919,6 @@ const styimat = (function() {
|
|
|
779
919
|
return [r, g, b];
|
|
780
920
|
};
|
|
781
921
|
|
|
782
|
-
// 3. sRGB Gamma 校正
|
|
783
922
|
const applyGamma = (c) => {
|
|
784
923
|
const sign = c < 0 ? -1 : 1;
|
|
785
924
|
const absC = Math.abs(c);
|
|
@@ -791,12 +930,10 @@ const styimat = (function() {
|
|
|
791
930
|
}
|
|
792
931
|
};
|
|
793
932
|
|
|
794
|
-
// 4. 钳制到 [0, 255]
|
|
795
933
|
const clamp = (value) => {
|
|
796
934
|
return Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
797
935
|
};
|
|
798
936
|
|
|
799
|
-
// 执行转换
|
|
800
937
|
const [X, Y, Z] = labToXyz(L, a, b);
|
|
801
938
|
const [linearR, linearG, linearB] = xyzToLinearRgb(X, Y, Z);
|
|
802
939
|
|
|
@@ -804,7 +941,6 @@ const styimat = (function() {
|
|
|
804
941
|
const g = applyGamma(linearG);
|
|
805
942
|
const bOut = applyGamma(linearB);
|
|
806
943
|
|
|
807
|
-
// 返回钳制后的 RGB 值
|
|
808
944
|
return {
|
|
809
945
|
r: clamp(r),
|
|
810
946
|
g: clamp(g),
|
|
@@ -814,13 +950,8 @@ const styimat = (function() {
|
|
|
814
950
|
|
|
815
951
|
/**
|
|
816
952
|
* Lab -> Display P3 转换
|
|
817
|
-
* @param {number} L - 明度 0-100
|
|
818
|
-
* @param {number} a - a分量
|
|
819
|
-
* @param {number} b - b分量
|
|
820
|
-
* @returns {Object} P3颜色 {r, g, b}
|
|
821
953
|
*/
|
|
822
954
|
function labToP3(L, a, b) {
|
|
823
|
-
// 1. Lab -> XYZ (使用与RGB相同的转换)
|
|
824
955
|
const labToXyz = (L, a, b) => {
|
|
825
956
|
const refX = 0.95047;
|
|
826
957
|
const refY = 1.00000;
|
|
@@ -844,9 +975,7 @@ const styimat = (function() {
|
|
|
844
975
|
];
|
|
845
976
|
};
|
|
846
977
|
|
|
847
|
-
// 2. XYZ -> Linear P3
|
|
848
978
|
const xyzToLinearP3 = (x, y, z) => {
|
|
849
|
-
// Display P3 转换矩阵 (D65)
|
|
850
979
|
const M = [
|
|
851
980
|
[ 2.493496911941425, -0.9313836179191239, -0.40271078445071684],
|
|
852
981
|
[-0.8294889695615747, 1.7626640603183463, 0.023624685841943577],
|
|
@@ -860,7 +989,6 @@ const styimat = (function() {
|
|
|
860
989
|
return [r, g, b];
|
|
861
990
|
};
|
|
862
991
|
|
|
863
|
-
// 3. P3 Gamma 校正(与sRGB相同)
|
|
864
992
|
const applyGamma = (c) => {
|
|
865
993
|
const sign = c < 0 ? -1 : 1;
|
|
866
994
|
const absC = Math.abs(c);
|
|
@@ -872,12 +1000,10 @@ const styimat = (function() {
|
|
|
872
1000
|
}
|
|
873
1001
|
};
|
|
874
1002
|
|
|
875
|
-
// 4. 钳制到 [0, 255],然后转换为 [0, 1] 范围
|
|
876
1003
|
const clamp = (value) => {
|
|
877
1004
|
return Math.max(0, Math.min(255, Math.round(value * 255)));
|
|
878
1005
|
};
|
|
879
1006
|
|
|
880
|
-
// 执行转换
|
|
881
1007
|
try {
|
|
882
1008
|
const [X, Y, Z] = labToXyz(L, a, b);
|
|
883
1009
|
const [linearR, linearG, linearB] = xyzToLinearP3(X, Y, Z);
|
|
@@ -886,7 +1012,6 @@ const styimat = (function() {
|
|
|
886
1012
|
const g = applyGamma(linearG);
|
|
887
1013
|
const bOut = applyGamma(linearB);
|
|
888
1014
|
|
|
889
|
-
// 转换为0-1范围的浮点数用于P3颜色格式
|
|
890
1015
|
return {
|
|
891
1016
|
r: Math.max(0, Math.min(1, r)),
|
|
892
1017
|
g: Math.max(0, Math.min(1, g)),
|
|
@@ -904,24 +1029,16 @@ const styimat = (function() {
|
|
|
904
1029
|
}
|
|
905
1030
|
|
|
906
1031
|
/**
|
|
907
|
-
*
|
|
908
|
-
* @param {number} L - Lab L值
|
|
909
|
-
* @param {number} a - Lab a值
|
|
910
|
-
* @param {number} b - Lab b值
|
|
911
|
-
* @param {Object} config - 配置对象
|
|
912
|
-
* @param {number|null} alpha - 透明度值
|
|
913
|
-
* @returns {string} CSS颜色字符串
|
|
1032
|
+
* 生成颜色字符串
|
|
914
1033
|
*/
|
|
915
1034
|
function generateColorString(L, a, b, config, alpha = null) {
|
|
916
1035
|
if (!config.enableP3 || !detectP3Support()) {
|
|
917
|
-
// 使用RGB
|
|
918
1036
|
const rgb = preciseLabToRGB(L, a, b);
|
|
919
1037
|
if (alpha !== null) {
|
|
920
1038
|
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
|
|
921
1039
|
}
|
|
922
1040
|
return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
|
|
923
1041
|
} else {
|
|
924
|
-
// 使用P3
|
|
925
1042
|
const p3 = labToP3(L, a, b);
|
|
926
1043
|
if (alpha !== null) {
|
|
927
1044
|
return `color(display-p3 ${p3.r.toFixed(4)} ${p3.g.toFixed(4)} ${p3.b.toFixed(4)} / ${alpha})`;
|
|
@@ -931,20 +1048,15 @@ const styimat = (function() {
|
|
|
931
1048
|
}
|
|
932
1049
|
|
|
933
1050
|
/**
|
|
934
|
-
* 处理CSS
|
|
935
|
-
* @param {string} value - CSS属性值
|
|
936
|
-
* @param {Object} config - 配置对象
|
|
937
|
-
* @returns {string} 处理后的值
|
|
1051
|
+
* 处理CSS值
|
|
938
1052
|
*/
|
|
939
1053
|
function processCSSValue(value, config) {
|
|
940
1054
|
let result = value;
|
|
941
1055
|
|
|
942
|
-
// 1. 首先处理math()函数
|
|
943
1056
|
if (config.enableMath) {
|
|
944
1057
|
result = processMathFunctions(result, config);
|
|
945
1058
|
}
|
|
946
1059
|
|
|
947
|
-
// 2. 然后处理LAB和LCH颜色
|
|
948
1060
|
if (config.convertLabToRGB || config.convertLchToRGB) {
|
|
949
1061
|
result = convertAllLabLchColors(result, config);
|
|
950
1062
|
}
|
|
@@ -952,7 +1064,9 @@ const styimat = (function() {
|
|
|
952
1064
|
return result;
|
|
953
1065
|
}
|
|
954
1066
|
|
|
955
|
-
|
|
1067
|
+
/**
|
|
1068
|
+
* 提取变量定义并移除
|
|
1069
|
+
*/
|
|
956
1070
|
function extractVariablesAndCSS(cssText, config) {
|
|
957
1071
|
const lines = cssText.split('\n');
|
|
958
1072
|
const globalVariables = {};
|
|
@@ -965,12 +1079,10 @@ const styimat = (function() {
|
|
|
965
1079
|
for (let line of lines) {
|
|
966
1080
|
const trimmed = line.trim();
|
|
967
1081
|
|
|
968
|
-
// 检查是否是变量定义
|
|
969
1082
|
const varMatch = trimmed.match(/^\$([a-zA-Z0-9_-]+)\s*:\s*(.+?);?$/);
|
|
970
1083
|
|
|
971
1084
|
if (varMatch) {
|
|
972
1085
|
const [, varName, varValue] = varMatch;
|
|
973
|
-
// 处理变量值中的数学表达式和颜色转换
|
|
974
1086
|
const processedValue = processCSSValue(
|
|
975
1087
|
replaceVariableUsesInValue(varValue.trim(), {
|
|
976
1088
|
...globalVariables,
|
|
@@ -980,19 +1092,16 @@ const styimat = (function() {
|
|
|
980
1092
|
);
|
|
981
1093
|
|
|
982
1094
|
if (currentSelector) {
|
|
983
|
-
// 选择器内部的变量
|
|
984
1095
|
if (!selectorVariables.has(currentSelector)) {
|
|
985
1096
|
selectorVariables.set(currentSelector, {});
|
|
986
1097
|
}
|
|
987
1098
|
selectorVariables.get(currentSelector)[varName] = processedValue;
|
|
988
1099
|
} else {
|
|
989
|
-
// 全局变量(:root 中)
|
|
990
1100
|
globalVariables[varName] = processedValue;
|
|
991
1101
|
}
|
|
992
1102
|
continue;
|
|
993
1103
|
}
|
|
994
1104
|
|
|
995
|
-
// 检查是否是选择器开始
|
|
996
1105
|
if (trimmed.endsWith('{')) {
|
|
997
1106
|
currentSelector = trimmed.slice(0, -1).trim();
|
|
998
1107
|
inSelectorBlock = true;
|
|
@@ -1000,10 +1109,8 @@ const styimat = (function() {
|
|
|
1000
1109
|
continue;
|
|
1001
1110
|
}
|
|
1002
1111
|
|
|
1003
|
-
// 检查是否是选择器结束
|
|
1004
1112
|
if (trimmed === '}') {
|
|
1005
1113
|
if (inSelectorBlock) {
|
|
1006
|
-
// 在选择器结束前插入变量声明
|
|
1007
1114
|
if (currentSelector && selectorVariables.has(currentSelector)) {
|
|
1008
1115
|
const vars = selectorVariables.get(currentSelector);
|
|
1009
1116
|
const indent = ' '.repeat(currentIndent);
|
|
@@ -1018,7 +1125,6 @@ const styimat = (function() {
|
|
|
1018
1125
|
|
|
1019
1126
|
cssWithoutVars += line + '\n';
|
|
1020
1127
|
|
|
1021
|
-
// 更新缩进级别
|
|
1022
1128
|
if (line.includes('{')) {
|
|
1023
1129
|
currentIndent += config.indentSize;
|
|
1024
1130
|
}
|
|
@@ -1034,10 +1140,12 @@ const styimat = (function() {
|
|
|
1034
1140
|
};
|
|
1035
1141
|
}
|
|
1036
1142
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1143
|
+
/**
|
|
1144
|
+
* 解析嵌套规则
|
|
1145
|
+
*/
|
|
1146
|
+
function parseNestedRules(cssText, config, aliases) {
|
|
1039
1147
|
const lines = cssText.split('\n');
|
|
1040
|
-
const stack = [];
|
|
1148
|
+
const stack = [];
|
|
1041
1149
|
const rootRules = [];
|
|
1042
1150
|
let currentIndent = 0;
|
|
1043
1151
|
|
|
@@ -1047,49 +1155,40 @@ const styimat = (function() {
|
|
|
1047
1155
|
|
|
1048
1156
|
if (!trimmed) continue;
|
|
1049
1157
|
|
|
1050
|
-
// 计算缩进级别(假设使用空格缩进)
|
|
1051
1158
|
const indent = line.match(/^(\s*)/)[0].length;
|
|
1052
1159
|
|
|
1053
|
-
// 处理规则块结束
|
|
1054
1160
|
if (trimmed === '}') {
|
|
1055
1161
|
if (stack.length > 0) {
|
|
1056
1162
|
const rule = stack.pop();
|
|
1057
1163
|
|
|
1058
|
-
// 如果这个规则有父规则,添加到父规则的children中
|
|
1059
1164
|
if (stack.length > 0) {
|
|
1060
1165
|
const parent = stack[stack.length - 1];
|
|
1061
1166
|
if (!parent.children) parent.children = [];
|
|
1062
1167
|
parent.children.push(rule);
|
|
1063
1168
|
} else {
|
|
1064
|
-
// 否则是根级规则
|
|
1065
1169
|
rootRules.push(rule);
|
|
1066
1170
|
}
|
|
1067
1171
|
}
|
|
1068
1172
|
continue;
|
|
1069
1173
|
}
|
|
1070
1174
|
|
|
1071
|
-
// 处理规则开始
|
|
1072
1175
|
if (trimmed.endsWith('{')) {
|
|
1073
1176
|
const selector = trimmed.slice(0, -1).trim();
|
|
1074
1177
|
|
|
1075
|
-
// 创建新规则
|
|
1076
1178
|
const rule = {
|
|
1077
1179
|
selector: selector,
|
|
1078
1180
|
properties: [],
|
|
1079
1181
|
children: []
|
|
1080
1182
|
};
|
|
1081
1183
|
|
|
1082
|
-
// 推入堆栈
|
|
1083
1184
|
stack.push(rule);
|
|
1084
1185
|
continue;
|
|
1085
1186
|
}
|
|
1086
1187
|
|
|
1087
|
-
// 处理属性行(不包含 { 或 } 的行)
|
|
1088
1188
|
if (!trimmed.includes('{') && !trimmed.includes('}') && trimmed.includes(':')) {
|
|
1089
1189
|
if (stack.length > 0) {
|
|
1090
1190
|
const currentRule = stack[stack.length - 1];
|
|
1091
|
-
|
|
1092
|
-
const parsed = parseSingleLineCSS(trimmed);
|
|
1191
|
+
const parsed = parseSingleLineCSS(trimmed, aliases);
|
|
1093
1192
|
parsed.forEach(obj => {
|
|
1094
1193
|
const key = Object.keys(obj)[0];
|
|
1095
1194
|
const value = processCSSValue(obj[key], config);
|
|
@@ -1100,7 +1199,6 @@ const styimat = (function() {
|
|
|
1100
1199
|
}
|
|
1101
1200
|
}
|
|
1102
1201
|
|
|
1103
|
-
// 处理栈中剩余规则
|
|
1104
1202
|
while (stack.length > 0) {
|
|
1105
1203
|
const rule = stack.pop();
|
|
1106
1204
|
if (stack.length === 0) {
|
|
@@ -1112,17 +1210,17 @@ const styimat = (function() {
|
|
|
1112
1210
|
}
|
|
1113
1211
|
}
|
|
1114
1212
|
|
|
1115
|
-
|
|
1116
|
-
return convertRulesToCSS(rootRules, config);
|
|
1213
|
+
return convertRulesToCSS(rootRules, config, '', aliases);
|
|
1117
1214
|
}
|
|
1118
1215
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1216
|
+
/**
|
|
1217
|
+
* 将规则树转换为CSS字符串
|
|
1218
|
+
*/
|
|
1219
|
+
function convertRulesToCSS(rules, config, parentSelector = '', aliases=[]) {
|
|
1121
1220
|
let result = '';
|
|
1122
1221
|
const isParentAt = parentSelector.startsWith("@");
|
|
1123
1222
|
|
|
1124
1223
|
for (const rule of rules) {
|
|
1125
|
-
// 构建完整选择器
|
|
1126
1224
|
const isAt = rule.selector.startsWith("@");
|
|
1127
1225
|
let fullSelector = (isParentAt ? " ".repeat(config.indentSize) : '') + rule.selector;
|
|
1128
1226
|
|
|
@@ -1131,7 +1229,6 @@ const styimat = (function() {
|
|
|
1131
1229
|
}
|
|
1132
1230
|
|
|
1133
1231
|
if (parentSelector) {
|
|
1134
|
-
// 处理 & 选择器
|
|
1135
1232
|
if (fullSelector.includes('&')) {
|
|
1136
1233
|
fullSelector = fullSelector.replace(/&/g, parentSelector);
|
|
1137
1234
|
} else if (fullSelector.trim().startsWith(':')) {
|
|
@@ -1141,12 +1238,10 @@ const styimat = (function() {
|
|
|
1141
1238
|
}
|
|
1142
1239
|
}
|
|
1143
1240
|
|
|
1144
|
-
// 如果有属性,生成规则块
|
|
1145
1241
|
if (rule.properties.length > 0) {
|
|
1146
1242
|
result += (isAt ? "" : fullSelector) + ' {\n';
|
|
1147
1243
|
for (const prop of rule.properties) {
|
|
1148
|
-
|
|
1149
|
-
const parsed = parseSingleLineCSS(prop);
|
|
1244
|
+
const parsed = parseSingleLineCSS(prop, aliases);
|
|
1150
1245
|
parsed.forEach(obj => {
|
|
1151
1246
|
const key = Object.keys(obj)[0];
|
|
1152
1247
|
const value = processCSSValue(obj[key], config);
|
|
@@ -1156,9 +1251,8 @@ const styimat = (function() {
|
|
|
1156
1251
|
result += isParentAt ? " ".repeat(config.indentSize) + '}\n' : '}\n\n';
|
|
1157
1252
|
}
|
|
1158
1253
|
|
|
1159
|
-
// 递归处理子规则
|
|
1160
1254
|
if (rule.children && rule.children.length > 0) {
|
|
1161
|
-
result += convertRulesToCSS(rule.children, config, fullSelector);
|
|
1255
|
+
result += convertRulesToCSS(rule.children, config, fullSelector, aliases);
|
|
1162
1256
|
}
|
|
1163
1257
|
|
|
1164
1258
|
if (isParentAt){
|
|
@@ -1169,7 +1263,9 @@ const styimat = (function() {
|
|
|
1169
1263
|
return result.trim() + "\n\n";
|
|
1170
1264
|
}
|
|
1171
1265
|
|
|
1172
|
-
|
|
1266
|
+
/**
|
|
1267
|
+
* 替换变量值中的变量引用
|
|
1268
|
+
*/
|
|
1173
1269
|
function replaceVariableUsesInValue(value, variables) {
|
|
1174
1270
|
return value.replace(/\$([a-zA-Z0-9_-]+)/g, (match, varName) => {
|
|
1175
1271
|
if (variables[varName]) {
|
|
@@ -1179,22 +1275,24 @@ const styimat = (function() {
|
|
|
1179
1275
|
});
|
|
1180
1276
|
}
|
|
1181
1277
|
|
|
1182
|
-
|
|
1278
|
+
/**
|
|
1279
|
+
* 替换变量使用
|
|
1280
|
+
*/
|
|
1183
1281
|
function replaceVariableUses(cssText, globalVariables, selectorVariables, config) {
|
|
1184
1282
|
let result = cssText;
|
|
1185
1283
|
|
|
1186
|
-
// 处理变量
|
|
1187
1284
|
result = result.replace(/(?:\$\$|\$)([a-zA-Z0-9_-]+)/g, (match, varName) => {
|
|
1188
1285
|
return match.startsWith('$$') ? `attr(${varName})` : `var(--${varName})`;
|
|
1189
1286
|
});
|
|
1190
1287
|
|
|
1191
|
-
// 最后处理所有的LAB、LCH颜色和math()函数
|
|
1192
1288
|
result = processCSSValue(result, config);
|
|
1193
1289
|
|
|
1194
1290
|
return result;
|
|
1195
1291
|
}
|
|
1196
1292
|
|
|
1197
|
-
|
|
1293
|
+
/**
|
|
1294
|
+
* 生成根规则
|
|
1295
|
+
*/
|
|
1198
1296
|
function generateRootRule(variables, config) {
|
|
1199
1297
|
if (Object.keys(variables).length === 0) {
|
|
1200
1298
|
return '';
|
|
@@ -1213,7 +1311,9 @@ const styimat = (function() {
|
|
|
1213
1311
|
return `${config.rootSelector} {\n${declarations}\n}\n\n`;
|
|
1214
1312
|
}
|
|
1215
1313
|
|
|
1216
|
-
|
|
1314
|
+
/**
|
|
1315
|
+
* 注入选择器局部变量
|
|
1316
|
+
*/
|
|
1217
1317
|
function injectSelectorVariables(cssText, selectorVariables, config) {
|
|
1218
1318
|
let result = '';
|
|
1219
1319
|
const lines = cssText.split('\n');
|
|
@@ -1227,7 +1327,6 @@ const styimat = (function() {
|
|
|
1227
1327
|
}
|
|
1228
1328
|
|
|
1229
1329
|
if (trimmed === '}' && currentSelector) {
|
|
1230
|
-
// 在选择器结束前插入变量声明
|
|
1231
1330
|
if (selectorVariables.has(currentSelector)) {
|
|
1232
1331
|
const vars = selectorVariables.get(currentSelector);
|
|
1233
1332
|
const indent = line.match(/^(\s*)/)[0];
|
|
@@ -1238,7 +1337,6 @@ const styimat = (function() {
|
|
|
1238
1337
|
currentSelector = null;
|
|
1239
1338
|
}
|
|
1240
1339
|
|
|
1241
|
-
// 使用全局处理函数处理所有math()和颜色
|
|
1242
1340
|
result += processCSSValue(line, config) + '\n';
|
|
1243
1341
|
}
|
|
1244
1342
|
|
|
@@ -1247,43 +1345,31 @@ const styimat = (function() {
|
|
|
1247
1345
|
|
|
1248
1346
|
/**
|
|
1249
1347
|
* 处理 @import 语句
|
|
1250
|
-
* @param {string} cssText - CSS文本
|
|
1251
|
-
* @param {Object} config - 配置对象
|
|
1252
|
-
* @returns {Promise<string>} 处理后的CSS文本
|
|
1253
1348
|
*/
|
|
1254
1349
|
async function processImports(cssText, config) {
|
|
1255
|
-
// 匹配所有@import语句
|
|
1256
1350
|
const importRegex = /@import\s+([^;]+?)\s*;/g;
|
|
1257
1351
|
|
|
1258
|
-
// 递归处理函数
|
|
1259
1352
|
const processRecursive = async (text) => {
|
|
1260
|
-
// 使用replace更简单的方式
|
|
1261
1353
|
const importPromises = [];
|
|
1262
1354
|
|
|
1263
|
-
// 使用replace的回调函数收集所有import的promise
|
|
1264
1355
|
const processedText = text.replace(importRegex, (match, url) => {
|
|
1265
1356
|
const importPromise = fetchImportContent(url, config)
|
|
1266
1357
|
.then(importedCSS => {
|
|
1267
|
-
// 递归处理导入的CSS中的import
|
|
1268
1358
|
return processImports(importedCSS, config);
|
|
1269
1359
|
})
|
|
1270
1360
|
.catch(error => {
|
|
1271
1361
|
console.warn(`无法导入CSS文件: ${url}`, error);
|
|
1272
|
-
// 导入失败时返回空字符串
|
|
1273
1362
|
return '';
|
|
1274
1363
|
});
|
|
1275
1364
|
|
|
1276
1365
|
importPromises.push(importPromise);
|
|
1277
1366
|
|
|
1278
|
-
// 返回占位符
|
|
1279
1367
|
return `__IMPORT_PLACEHOLDER_${importPromises.length - 1}__`;
|
|
1280
1368
|
});
|
|
1281
1369
|
|
|
1282
|
-
// 等待所有import完成
|
|
1283
1370
|
if (importPromises.length > 0) {
|
|
1284
1371
|
const importedContents = await Promise.all(importPromises);
|
|
1285
1372
|
|
|
1286
|
-
// 替换占位符为实际内容
|
|
1287
1373
|
let finalText = processedText;
|
|
1288
1374
|
for (let i = 0; i < importedContents.length; i++) {
|
|
1289
1375
|
finalText = finalText.replace(`__IMPORT_PLACEHOLDER_${i}__`, importedContents[i]);
|
|
@@ -1300,35 +1386,27 @@ const styimat = (function() {
|
|
|
1300
1386
|
|
|
1301
1387
|
/**
|
|
1302
1388
|
* 获取导入的CSS内容
|
|
1303
|
-
* @param {string} url - CSS文件URL
|
|
1304
|
-
* @param {Object} config - 配置对象
|
|
1305
|
-
* @returns {Promise<string>} CSS内容
|
|
1306
1389
|
*/
|
|
1307
1390
|
async function fetchImportContent(url, config) {
|
|
1308
|
-
// 检查缓存
|
|
1309
1391
|
if (config.importCache && importCache.has(url)) {
|
|
1310
1392
|
return importCache.get(url);
|
|
1311
1393
|
}
|
|
1312
1394
|
|
|
1313
|
-
// 构建完整的URL
|
|
1314
1395
|
const fullUrl = config.importBaseUrl
|
|
1315
1396
|
? new URL(url, config.importBaseUrl).href
|
|
1316
1397
|
: url;
|
|
1317
1398
|
|
|
1318
1399
|
let content;
|
|
1319
1400
|
|
|
1320
|
-
// 检查是否为Node.js环境
|
|
1321
1401
|
const isNode = typeof process !== 'undefined' &&
|
|
1322
1402
|
process.versions &&
|
|
1323
1403
|
process.versions.node;
|
|
1324
1404
|
|
|
1325
1405
|
if (isNode && typeof require !== 'undefined') {
|
|
1326
|
-
// Node.js环境:使用fs模块
|
|
1327
1406
|
try {
|
|
1328
1407
|
const fs = require('fs');
|
|
1329
1408
|
const path = require('path');
|
|
1330
1409
|
|
|
1331
|
-
// 处理相对路径
|
|
1332
1410
|
let filePath = fullUrl;
|
|
1333
1411
|
if (fullUrl.startsWith('.')) {
|
|
1334
1412
|
filePath = path.join(process.cwd(), fullUrl);
|
|
@@ -1339,7 +1417,6 @@ const styimat = (function() {
|
|
|
1339
1417
|
throw new Error(`Node.js文件读取失败: ${fullUrl} - ${error.message}`);
|
|
1340
1418
|
}
|
|
1341
1419
|
} else {
|
|
1342
|
-
// 浏览器环境:使用fetch
|
|
1343
1420
|
try {
|
|
1344
1421
|
const controller = new AbortController();
|
|
1345
1422
|
const timeoutId = setTimeout(() => controller.abort(), config.importTimeout);
|
|
@@ -1366,7 +1443,6 @@ const styimat = (function() {
|
|
|
1366
1443
|
}
|
|
1367
1444
|
}
|
|
1368
1445
|
|
|
1369
|
-
// 缓存内容
|
|
1370
1446
|
if (config.importCache) {
|
|
1371
1447
|
importCache.set(url, content);
|
|
1372
1448
|
}
|
|
@@ -1376,73 +1452,82 @@ const styimat = (function() {
|
|
|
1376
1452
|
|
|
1377
1453
|
/**
|
|
1378
1454
|
* 主转换函数
|
|
1379
|
-
* @param {string} cssText - CSS文本
|
|
1380
|
-
* @param {Object} customConfig - 自定义配置
|
|
1381
|
-
* @returns {string|Promise<string>} 转换后的CSS(如果是异步则为Promise)
|
|
1382
1455
|
*/
|
|
1383
1456
|
function convert(cssText, customConfig = {}) {
|
|
1384
|
-
// 处理同步和异步版本
|
|
1385
1457
|
const syncConvert = (cssText, config) => {
|
|
1386
|
-
// 先解析配置头
|
|
1387
1458
|
const { config: headerConfig, css: cleanedCSS } = parseConfigHeader(cssText);
|
|
1388
1459
|
const finalConfig = { ...defaultConfig, ...customConfig,...headerConfig };
|
|
1389
1460
|
|
|
1390
|
-
|
|
1461
|
+
let cssWithAliases = cleanedCSS;
|
|
1462
|
+
let aliases = new Map();
|
|
1463
|
+
if (finalConfig.enableAlias) {
|
|
1464
|
+
const { aliases: parsedAliases, css: cssWithoutAliases } = parseAliasStatements(cssWithAliases);
|
|
1465
|
+
aliases = parsedAliases;
|
|
1466
|
+
cssWithAliases = cssWithoutAliases;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
let cssWithMacros = cssWithAliases;
|
|
1470
|
+
let macros = new Map();
|
|
1471
|
+
if (finalConfig.enableMacros) {
|
|
1472
|
+
const { macros: parsedMacros, css: cssWithoutMacros } = parseMacroStatements(cssWithMacros, finalConfig);
|
|
1473
|
+
macros = parsedMacros;
|
|
1474
|
+
cssWithMacros = cssWithoutMacros;
|
|
1475
|
+
|
|
1476
|
+
for (const [name, macro] of macros) {
|
|
1477
|
+
macroRegistry.set(name, macro);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
let cssAfterMacros = cssWithMacros;
|
|
1482
|
+
if (finalConfig.enableMacros && macros.size > 0) {
|
|
1483
|
+
cssAfterMacros = applyMacroCalls(cssAfterMacros, macros, finalConfig);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1391
1486
|
const { globalVariables, selectorVariables, cssWithoutVars } =
|
|
1392
|
-
extractVariablesAndCSS(
|
|
1487
|
+
extractVariablesAndCSS(cssAfterMacros, finalConfig);
|
|
1393
1488
|
|
|
1394
|
-
// 2. 解析嵌套规则(如果启用)
|
|
1395
1489
|
let processedCSS = cssWithoutVars.trim();
|
|
1396
1490
|
if (finalConfig.enableNesting && cssWithoutVars.includes('{')) {
|
|
1397
1491
|
try {
|
|
1398
|
-
processedCSS = parseNestedRules(cssWithoutVars, finalConfig);
|
|
1492
|
+
processedCSS = parseNestedRules(cssWithoutVars, finalConfig, aliases);
|
|
1399
1493
|
} catch (error) {
|
|
1400
1494
|
console.warn('嵌套解析失败,使用原始CSS:', error);
|
|
1401
1495
|
}
|
|
1402
1496
|
}
|
|
1403
1497
|
|
|
1404
|
-
// 3. 生成根规则(全局变量)
|
|
1405
1498
|
const rootRule = generateRootRule(globalVariables, finalConfig);
|
|
1406
1499
|
|
|
1407
|
-
// 4. 注入选择器局部变量
|
|
1408
1500
|
let finalCSS = processedCSS;
|
|
1409
1501
|
if (selectorVariables.size > 0) {
|
|
1410
1502
|
finalCSS = injectSelectorVariables(processedCSS, selectorVariables, finalConfig);
|
|
1411
1503
|
}
|
|
1412
1504
|
|
|
1413
|
-
// 5. 替换变量使用
|
|
1414
1505
|
finalCSS = replaceVariableUses(finalCSS, globalVariables, selectorVariables, finalConfig);
|
|
1415
1506
|
|
|
1416
|
-
// 6. 组合结果
|
|
1417
1507
|
return rootRule + finalCSS;
|
|
1418
1508
|
};
|
|
1419
1509
|
|
|
1420
|
-
// 检查是否有@import语句(需要异步处理)
|
|
1421
1510
|
const hasImports = cssText && /@import\s+([^;]+?)\s*;/g.test(cssText);
|
|
1422
1511
|
const finalConfig = { ...defaultConfig, ...customConfig };
|
|
1423
1512
|
|
|
1424
1513
|
if (!hasImports) {
|
|
1425
|
-
// 没有@import,同步处理
|
|
1426
1514
|
return syncConvert(cssText, finalConfig);
|
|
1427
1515
|
} else {
|
|
1428
|
-
// 有@import,异步处理
|
|
1429
1516
|
return (async () => {
|
|
1430
1517
|
try {
|
|
1431
|
-
// 处理@import
|
|
1432
1518
|
const processedWithImports = await processImports(cssText, finalConfig);
|
|
1433
|
-
|
|
1434
|
-
// 同步处理剩余部分
|
|
1435
1519
|
return syncConvert(processedWithImports, finalConfig);
|
|
1436
1520
|
} catch (error) {
|
|
1437
1521
|
console.error('@import处理失败:', error);
|
|
1438
|
-
|
|
1439
|
-
return syncConvert(cleanedCSS, finalConfig);
|
|
1522
|
+
return syncConvert(cssText, finalConfig);
|
|
1440
1523
|
}
|
|
1441
1524
|
})();
|
|
1442
1525
|
}
|
|
1443
1526
|
}
|
|
1444
1527
|
|
|
1445
|
-
|
|
1528
|
+
/**
|
|
1529
|
+
* 自动处理带有特定属性的 style 标签
|
|
1530
|
+
*/
|
|
1446
1531
|
function autoProcessStyleTags(customConfig = {}) {
|
|
1447
1532
|
const config = { ...defaultConfig, ...customConfig };
|
|
1448
1533
|
const styleTags = document.querySelectorAll(`style[${config.styleTagAttribute || 'e'}]`);
|
|
@@ -1450,7 +1535,6 @@ const styimat = (function() {
|
|
|
1450
1535
|
styleTags.forEach(styleTag => {
|
|
1451
1536
|
let originalCSS = styleTag.textContent;
|
|
1452
1537
|
|
|
1453
|
-
// 处理@import(如果是异步的)
|
|
1454
1538
|
const processStyleTag = async () => {
|
|
1455
1539
|
try {
|
|
1456
1540
|
if (styleTag.getAttribute("src")){
|
|
@@ -1458,14 +1542,11 @@ const styimat = (function() {
|
|
|
1458
1542
|
}
|
|
1459
1543
|
const convertedCSS = await convert(originalCSS, config);
|
|
1460
1544
|
|
|
1461
|
-
// 创建新的 style 标签
|
|
1462
1545
|
const newStyleTag = document.createElement('style');
|
|
1463
1546
|
newStyleTag.textContent = convertedCSS;
|
|
1464
1547
|
|
|
1465
|
-
// 插入到原标签后面
|
|
1466
1548
|
styleTag.parentNode.insertBefore(newStyleTag, styleTag.nextSibling);
|
|
1467
1549
|
|
|
1468
|
-
// 可选:移除原标签
|
|
1469
1550
|
if (!config.preserveOriginal) {
|
|
1470
1551
|
styleTag.remove();
|
|
1471
1552
|
} else {
|
|
@@ -1486,16 +1567,16 @@ const styimat = (function() {
|
|
|
1486
1567
|
defaultConfig = { ...defaultConfig, ...config };
|
|
1487
1568
|
}
|
|
1488
1569
|
|
|
1489
|
-
|
|
1570
|
+
/**
|
|
1571
|
+
* 应用CSS到页面
|
|
1572
|
+
*/
|
|
1490
1573
|
function apply(cssText, customConfig = {}) {
|
|
1491
1574
|
const config = { ...defaultConfig, ...customConfig };
|
|
1492
1575
|
|
|
1493
1576
|
if (cssText) {
|
|
1494
|
-
// 检查是否有@import
|
|
1495
1577
|
const hasImports = /@import\s+([^;]+?)\s*;/g.test(cssText);
|
|
1496
1578
|
|
|
1497
1579
|
if (hasImports) {
|
|
1498
|
-
// 异步处理
|
|
1499
1580
|
return (async () => {
|
|
1500
1581
|
try {
|
|
1501
1582
|
const converted = await convert(cssText, config);
|
|
@@ -1509,7 +1590,6 @@ const styimat = (function() {
|
|
|
1509
1590
|
}
|
|
1510
1591
|
})();
|
|
1511
1592
|
} else {
|
|
1512
|
-
// 同步处理
|
|
1513
1593
|
const converted = convert(cssText, config);
|
|
1514
1594
|
const styleEl = document.createElement('style');
|
|
1515
1595
|
styleEl.textContent = converted;
|
|
@@ -1527,84 +1607,110 @@ const styimat = (function() {
|
|
|
1527
1607
|
}
|
|
1528
1608
|
}
|
|
1529
1609
|
|
|
1530
|
-
//
|
|
1610
|
+
// 创建公共 API
|
|
1531
1611
|
const api = {
|
|
1532
1612
|
convert,
|
|
1533
1613
|
apply,
|
|
1534
1614
|
config,
|
|
1535
1615
|
|
|
1536
|
-
// 检测P3支持
|
|
1537
1616
|
supportsP3: detectP3Support(),
|
|
1538
1617
|
|
|
1539
|
-
// 重新检测P3支持(如果配置变化)
|
|
1540
1618
|
detectP3Support: detectP3Support,
|
|
1541
1619
|
|
|
1542
|
-
// 导入相关方法
|
|
1543
1620
|
imports: {
|
|
1544
|
-
/**
|
|
1545
|
-
* 清除导入缓存
|
|
1546
|
-
*/
|
|
1547
1621
|
clearCache: function() {
|
|
1548
1622
|
importCache.clear();
|
|
1549
1623
|
},
|
|
1550
|
-
|
|
1551
|
-
/**
|
|
1552
|
-
* 设置导入基础URL
|
|
1553
|
-
* @param {string} baseUrl - 基础URL
|
|
1554
|
-
*/
|
|
1555
1624
|
setBaseUrl: function(baseUrl) {
|
|
1556
1625
|
defaultConfig.importBaseUrl = baseUrl;
|
|
1557
1626
|
},
|
|
1558
|
-
|
|
1559
|
-
/**
|
|
1560
|
-
* 启用/禁用导入缓存
|
|
1561
|
-
* @param {boolean} enabled - 是否启用缓存
|
|
1562
|
-
*/
|
|
1563
1627
|
setCacheEnabled: function(enabled) {
|
|
1564
1628
|
defaultConfig.importCache = enabled;
|
|
1565
1629
|
},
|
|
1566
|
-
|
|
1567
|
-
/**
|
|
1568
|
-
* 设置导入超时时间
|
|
1569
|
-
* @param {number} timeout - 超时时间(毫秒)
|
|
1570
|
-
*/
|
|
1571
1630
|
setTimeout: function(timeout) {
|
|
1572
1631
|
defaultConfig.importTimeout = timeout;
|
|
1573
1632
|
}
|
|
1574
1633
|
},
|
|
1575
1634
|
|
|
1576
|
-
|
|
1635
|
+
aliases: {
|
|
1636
|
+
add: function(alias, property) {
|
|
1637
|
+
aliasMap.set(alias, property);
|
|
1638
|
+
},
|
|
1639
|
+
remove: function(alias) {
|
|
1640
|
+
aliasMap.delete(alias);
|
|
1641
|
+
},
|
|
1642
|
+
getAll: function() {
|
|
1643
|
+
return Array.from(aliasMap.entries());
|
|
1644
|
+
},
|
|
1645
|
+
clear: function() {
|
|
1646
|
+
aliasMap.clear();
|
|
1647
|
+
}
|
|
1648
|
+
},
|
|
1649
|
+
|
|
1650
|
+
macros: {
|
|
1651
|
+
define: function(name, body, params = []) {
|
|
1652
|
+
if (typeof body === 'function') {
|
|
1653
|
+
const funcString = body.toString();
|
|
1654
|
+
const bodyMatch = funcString.match(/{([\s\S]*)}/);
|
|
1655
|
+
if (bodyMatch) {
|
|
1656
|
+
macroRegistry.set(name, {
|
|
1657
|
+
params: params.map(p => typeof p === 'string' ? { name: p } : p),
|
|
1658
|
+
body: bodyMatch[1].trim()
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
} else {
|
|
1662
|
+
macroRegistry.set(name, {
|
|
1663
|
+
params: params.map(p => typeof p === 'string' ? { name: p } : p),
|
|
1664
|
+
body: body
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
},
|
|
1668
|
+
|
|
1669
|
+
call: function(name, ...args) {
|
|
1670
|
+
const macro = macroRegistry.get(name);
|
|
1671
|
+
if (!macro) {
|
|
1672
|
+
throw new Error(`未定义的宏: ${name}`);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
const argsMap = new Map();
|
|
1676
|
+
for (let i = 0; i < macro.params.length; i++) {
|
|
1677
|
+
const param = macro.params[i];
|
|
1678
|
+
const value = i < args.length ? args[i] : param.defaultValue;
|
|
1679
|
+
if (value === undefined && param.defaultValue === null) {
|
|
1680
|
+
throw new Error(`缺少必需参数: ${param.name}`);
|
|
1681
|
+
}
|
|
1682
|
+
argsMap.set(param.name, value !== undefined ? value : '');
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
return applyMacro(macro.body, argsMap, defaultConfig);
|
|
1686
|
+
},
|
|
1687
|
+
|
|
1688
|
+
getAll: function() {
|
|
1689
|
+
return Array.from(macroRegistry.entries());
|
|
1690
|
+
},
|
|
1691
|
+
|
|
1692
|
+
remove: function(name) {
|
|
1693
|
+
macroRegistry.delete(name);
|
|
1694
|
+
},
|
|
1695
|
+
|
|
1696
|
+
clear: function() {
|
|
1697
|
+
macroRegistry.clear();
|
|
1698
|
+
},
|
|
1699
|
+
|
|
1700
|
+
parse: function(cssText) {
|
|
1701
|
+
const { macros } = parseMacroStatements(cssText, defaultConfig);
|
|
1702
|
+
for (const [name, macro] of macros) {
|
|
1703
|
+
macroRegistry.set(name, macro);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
},
|
|
1707
|
+
|
|
1577
1708
|
math: {
|
|
1578
|
-
/**
|
|
1579
|
-
* 计算数学表达式
|
|
1580
|
-
* @param {string} expression - 数学表达式
|
|
1581
|
-
* @returns {string} 计算结果
|
|
1582
|
-
*/
|
|
1583
1709
|
evaluate: function(expression) {
|
|
1584
1710
|
return evaluateMathExpression(expression, defaultConfig);
|
|
1585
1711
|
},
|
|
1586
|
-
|
|
1587
|
-
/**
|
|
1588
|
-
* 解析带单位的数值
|
|
1589
|
-
* @param {string} value - 带单位的字符串
|
|
1590
|
-
* @returns {Object} {value: number, unit: string}
|
|
1591
|
-
*/
|
|
1592
1712
|
parseUnit: parseValueWithUnit,
|
|
1593
|
-
|
|
1594
|
-
/**
|
|
1595
|
-
* 四舍五入数字
|
|
1596
|
-
* @param {number} num - 要四舍五入的数字
|
|
1597
|
-
* @param {number} precision - 精度
|
|
1598
|
-
* @returns {string} 四舍五入后的数字字符串
|
|
1599
|
-
*/
|
|
1600
1713
|
round: roundNumber,
|
|
1601
|
-
|
|
1602
|
-
/**
|
|
1603
|
-
* 测试数学表达式
|
|
1604
|
-
* @param {string} expression - 要测试的表达式
|
|
1605
|
-
* @param {Object} testConfig - 测试配置
|
|
1606
|
-
* @returns {Object} 测试结果
|
|
1607
|
-
*/
|
|
1608
1714
|
test: function(expression, testConfig = {}) {
|
|
1609
1715
|
const config = { ...defaultConfig, ...testConfig };
|
|
1610
1716
|
try {
|
|
@@ -1625,7 +1731,6 @@ const styimat = (function() {
|
|
|
1625
1731
|
}
|
|
1626
1732
|
},
|
|
1627
1733
|
|
|
1628
|
-
// 颜色转换工具方法
|
|
1629
1734
|
colorUtils: {
|
|
1630
1735
|
labToRGB: preciseLabToRGB,
|
|
1631
1736
|
lchToLab: lchToLab,
|
|
@@ -1667,26 +1772,11 @@ const styimat = (function() {
|
|
|
1667
1772
|
const lab = lchToLab(L, C, H);
|
|
1668
1773
|
return preciseLabToRGB(lab.L, lab.a, lab.b);
|
|
1669
1774
|
},
|
|
1670
|
-
/**
|
|
1671
|
-
* 生成颜色字符串(根据P3支持情况)
|
|
1672
|
-
* @param {number} L - Lab L值
|
|
1673
|
-
* @param {number} a - Lab a值
|
|
1674
|
-
* @param {number} b - Lab b值
|
|
1675
|
-
* @param {number|null} alpha - 透明度
|
|
1676
|
-
* @param {boolean} useP3 - 是否使用P3(自动检测)
|
|
1677
|
-
* @returns {string} CSS颜色字符串
|
|
1678
|
-
*/
|
|
1679
1775
|
generateColor: function(L, a, b, alpha = null, useP3 = true) {
|
|
1680
1776
|
return generateColorString(L, a, b, { enableP3: useP3 }, alpha);
|
|
1681
1777
|
},
|
|
1682
|
-
/**
|
|
1683
|
-
* 解析CSS颜色字符串
|
|
1684
|
-
* @param {string} colorString - CSS颜色字符串
|
|
1685
|
-
* @returns {Object} 包含原始Lab值和颜色字符串的对象
|
|
1686
|
-
*/
|
|
1687
1778
|
parseColor: function(colorString) {
|
|
1688
1779
|
try {
|
|
1689
|
-
// 尝试解析lab()函数格式
|
|
1690
1780
|
const labMatch = colorString.match(/lab\(\s*([\d.]+)(%?)\s+([\d.-]+)\s+([\d.-]+)(?:\s*\/\s*([\d.%]+))?\s*\)/i);
|
|
1691
1781
|
if (labMatch) {
|
|
1692
1782
|
let L = parseFloat(labMatch[1]);
|
|
@@ -1706,7 +1796,6 @@ const styimat = (function() {
|
|
|
1706
1796
|
};
|
|
1707
1797
|
}
|
|
1708
1798
|
|
|
1709
|
-
// 尝试解析lch()函数格式
|
|
1710
1799
|
const lchMatch = colorString.match(/lch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(deg)?(?:\s*\/\s*([\d.%]+))?\s*\)/i);
|
|
1711
1800
|
if (lchMatch) {
|
|
1712
1801
|
let L = parseFloat(lchMatch[1]);
|
|
@@ -1737,35 +1826,28 @@ const styimat = (function() {
|
|
|
1737
1826
|
}
|
|
1738
1827
|
};
|
|
1739
1828
|
|
|
1740
|
-
//
|
|
1829
|
+
// 创建主函数
|
|
1741
1830
|
const styimat = function(...args) {
|
|
1742
|
-
// 检查是否是模板字符串调用(标签函数)
|
|
1743
1831
|
if (args.length > 1 || (args[0] && args[0].raw)) {
|
|
1744
|
-
// 处理模板字符串
|
|
1745
1832
|
return handleTemplateTag(...args);
|
|
1746
1833
|
}
|
|
1747
1834
|
|
|
1748
|
-
// 获取第一个参数
|
|
1749
1835
|
const firstArg = args[0];
|
|
1750
1836
|
|
|
1751
|
-
// 如果传入CSS文本,则编译并返回结果
|
|
1752
1837
|
if (typeof firstArg === 'string') {
|
|
1753
1838
|
const result = convert(firstArg, { ...defaultConfig, ...args[1] });
|
|
1754
1839
|
|
|
1755
|
-
// 如果结果是Promise,返回Promise
|
|
1756
1840
|
if (result && typeof result.then === 'function') {
|
|
1757
1841
|
return result;
|
|
1758
1842
|
}
|
|
1759
1843
|
return result;
|
|
1760
1844
|
}
|
|
1761
1845
|
|
|
1762
|
-
// 如果传入配置对象,则应用配置
|
|
1763
1846
|
if (typeof firstArg === 'object' && firstArg !== null) {
|
|
1764
1847
|
defaultConfig = { ...defaultConfig, ...firstArg };
|
|
1765
1848
|
return styimat;
|
|
1766
1849
|
}
|
|
1767
1850
|
|
|
1768
|
-
// 如果没有参数,执行自动处理
|
|
1769
1851
|
if (args.length === 0) {
|
|
1770
1852
|
return apply();
|
|
1771
1853
|
}
|
|
@@ -1773,13 +1855,10 @@ const styimat = (function() {
|
|
|
1773
1855
|
return styimat;
|
|
1774
1856
|
};
|
|
1775
1857
|
|
|
1776
|
-
// 处理模板字符串的函数
|
|
1777
1858
|
function handleTemplateTag(strings, ...values) {
|
|
1778
|
-
// 拼接模板字符串
|
|
1779
1859
|
let cssText = strings[0];
|
|
1780
1860
|
|
|
1781
1861
|
for (let i = 0; i < values.length; i++) {
|
|
1782
|
-
// 处理插值(支持字符串、数字、函数等)
|
|
1783
1862
|
const value = values[i];
|
|
1784
1863
|
let result = '';
|
|
1785
1864
|
|
|
@@ -1794,17 +1873,14 @@ const styimat = (function() {
|
|
|
1794
1873
|
cssText += result + strings[i + 1];
|
|
1795
1874
|
}
|
|
1796
1875
|
|
|
1797
|
-
// 使用convert函数处理
|
|
1798
1876
|
const result = convert(cssText, defaultConfig);
|
|
1799
1877
|
|
|
1800
|
-
// 如果结果是Promise,返回Promise
|
|
1801
1878
|
if (result && typeof result.then === 'function') {
|
|
1802
1879
|
return result;
|
|
1803
1880
|
}
|
|
1804
1881
|
return result;
|
|
1805
1882
|
}
|
|
1806
1883
|
|
|
1807
|
-
// 将API的所有方法复制到主函数上
|
|
1808
1884
|
Object.assign(styimat, api);
|
|
1809
1885
|
Object.setPrototypeOf(styimat, Function.prototype);
|
|
1810
1886
|
|
|
@@ -1842,13 +1918,10 @@ const styimat = (function() {
|
|
|
1842
1918
|
})();
|
|
1843
1919
|
|
|
1844
1920
|
if (typeof define === 'function' && define.amd) {
|
|
1845
|
-
// AMD 支持 (RequireJS)
|
|
1846
1921
|
define([], ()=>styimat);
|
|
1847
1922
|
} else if (typeof module === 'object' && module.exports) {
|
|
1848
|
-
// CommonJS 支持 (Node.js, Browserify, Webpack)
|
|
1849
1923
|
module.exports = styimat;
|
|
1850
1924
|
} else {
|
|
1851
|
-
|
|
1852
|
-
const globalObj = globalThis ?? (typeof self !== 'undefined' && self) ?? (typeof window !== 'undefined' &window) ?? global ?? {};
|
|
1925
|
+
const globalObj = globalThis ?? (typeof self !== 'undefined' && self) ?? (typeof window !== 'undefined' && window) ?? global ?? {};
|
|
1853
1926
|
globalObj.styimat = styimat;
|
|
1854
1927
|
}
|