@melcanz85/chaincss 1.11.5 → 1.12.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/node/atomic-optimizer.js +275 -88
- package/node/btt.js +587 -150
- package/node/chaincss.js +189 -65
- package/node/strVal.js +2 -2
- package/package.json +69 -35
- package/types.d.ts +175 -61
package/node/btt.js
CHANGED
|
@@ -2,10 +2,24 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const https = require('https');
|
|
4
4
|
const { tokens, createTokens, responsive } = require('../shared/tokens.cjs');
|
|
5
|
+
const { AtomicOptimizer } = require('./atomic-optimizer');
|
|
6
|
+
|
|
7
|
+
const atomicOptimizer = new AtomicOptimizer({
|
|
8
|
+
enabled: false, // default off; turn on via configure()
|
|
9
|
+
alwaysAtomic: [],
|
|
10
|
+
neverAtomic: ['content', 'animation']
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function configureAtomic(opts) {
|
|
14
|
+
Object.assign(atomicOptimizer.options, opts);
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
const chain = {
|
|
6
18
|
cssOutput: undefined,
|
|
7
19
|
catcher: {},
|
|
8
20
|
cachedValidProperties: [],
|
|
21
|
+
classMap: {}, // For atomic CSS class mapping
|
|
22
|
+
atomicStats: null, // For atomic optimizer stats
|
|
9
23
|
|
|
10
24
|
initializeProperties() {
|
|
11
25
|
try {
|
|
@@ -14,7 +28,7 @@ const chain = {
|
|
|
14
28
|
const data = fs.readFileSync(jsonPath, 'utf8');
|
|
15
29
|
this.cachedValidProperties = JSON.parse(data);
|
|
16
30
|
} else {
|
|
17
|
-
console.log('
|
|
31
|
+
console.log('CSS properties not cached, will load on first use');
|
|
18
32
|
}
|
|
19
33
|
} catch (error) {
|
|
20
34
|
console.error('Error loading CSS properties:', error.message);
|
|
@@ -70,7 +84,9 @@ const chain = {
|
|
|
70
84
|
return this.cachedValidProperties;
|
|
71
85
|
}
|
|
72
86
|
};
|
|
87
|
+
|
|
73
88
|
chain.initializeProperties();
|
|
89
|
+
|
|
74
90
|
const resolveToken = (value, useTokens) => {
|
|
75
91
|
if (!useTokens || typeof value !== 'string' || !value.startsWith('$')) {
|
|
76
92
|
return value;
|
|
@@ -79,11 +95,14 @@ const resolveToken = (value, useTokens) => {
|
|
|
79
95
|
const tokenValue = tokens.get(tokenPath);
|
|
80
96
|
return tokenValue || value;
|
|
81
97
|
};
|
|
98
|
+
|
|
82
99
|
function $(useTokens = true) {
|
|
83
100
|
const catcher = {};
|
|
84
101
|
const validProperties = chain.cachedValidProperties;
|
|
102
|
+
|
|
85
103
|
const handler = {
|
|
86
104
|
get: (target, prop) => {
|
|
105
|
+
// Handle .block()
|
|
87
106
|
if (prop === 'block') {
|
|
88
107
|
return function(...args) {
|
|
89
108
|
if (args.length === 0) {
|
|
@@ -99,7 +118,44 @@ function $(useTokens = true) {
|
|
|
99
118
|
return result;
|
|
100
119
|
};
|
|
101
120
|
}
|
|
102
|
-
|
|
121
|
+
|
|
122
|
+
// Handle .hover()
|
|
123
|
+
if (prop === 'hover') {
|
|
124
|
+
return () => {
|
|
125
|
+
const hoverCatcher = {};
|
|
126
|
+
const hoverHandler = {
|
|
127
|
+
get: (hoverTarget, hoverProp) => {
|
|
128
|
+
if (hoverProp === 'end') {
|
|
129
|
+
return () => {
|
|
130
|
+
catcher.hover = { ...hoverCatcher };
|
|
131
|
+
Object.keys(hoverCatcher).forEach(key => delete hoverCatcher[key]);
|
|
132
|
+
return proxy;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const cssProperty = hoverProp.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
136
|
+
if (validProperties && validProperties.length > 0 && !validProperties.includes(cssProperty)) {
|
|
137
|
+
console.warn(`Warning: '${cssProperty}' may not be a valid CSS property`);
|
|
138
|
+
}
|
|
139
|
+
return (value) => {
|
|
140
|
+
hoverCatcher[hoverProp] = resolveToken(value, useTokens);
|
|
141
|
+
return hoverProxy;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const hoverProxy = new Proxy({}, hoverHandler);
|
|
146
|
+
return hoverProxy;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Handle .end()
|
|
151
|
+
if (prop === 'end') {
|
|
152
|
+
return () => {
|
|
153
|
+
return proxy;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle .select() - renamed from $ to avoid conflict
|
|
158
|
+
if (prop === 'select') {
|
|
103
159
|
return function(selector) {
|
|
104
160
|
const props = {};
|
|
105
161
|
const selectorProxy = new Proxy({}, {
|
|
@@ -114,14 +170,15 @@ function $(useTokens = true) {
|
|
|
114
170
|
}
|
|
115
171
|
return function(value) {
|
|
116
172
|
props[methodProp] = resolveToken(value, useTokens);
|
|
117
|
-
return selectorProxy;
|
|
173
|
+
return selectorProxy;
|
|
118
174
|
};
|
|
119
175
|
}
|
|
120
176
|
});
|
|
121
|
-
|
|
122
177
|
return selectorProxy;
|
|
123
178
|
};
|
|
124
179
|
}
|
|
180
|
+
|
|
181
|
+
// At-Rules
|
|
125
182
|
if (prop === 'media') {
|
|
126
183
|
return function(query, callback) {
|
|
127
184
|
const subChain = $(useTokens);
|
|
@@ -130,32 +187,30 @@ function $(useTokens = true) {
|
|
|
130
187
|
catcher.atRules.push({
|
|
131
188
|
type: 'media',
|
|
132
189
|
query: query,
|
|
133
|
-
styles:
|
|
190
|
+
styles: result
|
|
134
191
|
});
|
|
135
192
|
return proxy;
|
|
136
193
|
};
|
|
137
194
|
}
|
|
195
|
+
|
|
138
196
|
if (prop === 'keyframes') {
|
|
139
197
|
return function(name, callback) {
|
|
140
|
-
const keyframeContext = {
|
|
141
|
-
_keyframeSteps: {}
|
|
142
|
-
};
|
|
198
|
+
const keyframeContext = { _keyframeSteps: {} };
|
|
143
199
|
const keyframeProxy = new Proxy(keyframeContext, {
|
|
144
200
|
get: (target, stepProp) => {
|
|
145
|
-
if (stepProp === 'from' || stepProp === 'to'
|
|
146
|
-
return function(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
201
|
+
if (stepProp === 'from' || stepProp === 'to') {
|
|
202
|
+
return function(stepCallback) {
|
|
203
|
+
const subChain = $(useTokens);
|
|
204
|
+
const properties = stepCallback(subChain).block();
|
|
205
|
+
target._keyframeSteps[stepProp] = properties;
|
|
206
|
+
return keyframeProxy;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (stepProp === 'percent') {
|
|
210
|
+
return function(value, stepCallback) {
|
|
211
|
+
const subChain = $(useTokens);
|
|
212
|
+
const properties = stepCallback(subChain).block();
|
|
213
|
+
target._keyframeSteps[`${value}%`] = properties;
|
|
159
214
|
return keyframeProxy;
|
|
160
215
|
};
|
|
161
216
|
}
|
|
@@ -167,13 +222,12 @@ function $(useTokens = true) {
|
|
|
167
222
|
catcher.atRules.push({
|
|
168
223
|
type: 'keyframes',
|
|
169
224
|
name: name,
|
|
170
|
-
steps: keyframeContext._keyframeSteps
|
|
171
|
-
atomic: false
|
|
225
|
+
steps: keyframeContext._keyframeSteps
|
|
172
226
|
});
|
|
173
|
-
|
|
174
227
|
return proxy;
|
|
175
228
|
};
|
|
176
229
|
}
|
|
230
|
+
|
|
177
231
|
if (prop === 'fontFace') {
|
|
178
232
|
return function(callback) {
|
|
179
233
|
const subChain = $(useTokens);
|
|
@@ -181,13 +235,12 @@ function $(useTokens = true) {
|
|
|
181
235
|
if (!catcher.atRules) catcher.atRules = [];
|
|
182
236
|
catcher.atRules.push({
|
|
183
237
|
type: 'font-face',
|
|
184
|
-
properties: fontProps
|
|
185
|
-
atomic: false
|
|
238
|
+
properties: fontProps
|
|
186
239
|
});
|
|
187
|
-
|
|
188
240
|
return proxy;
|
|
189
241
|
};
|
|
190
242
|
}
|
|
243
|
+
|
|
191
244
|
if (prop === 'supports') {
|
|
192
245
|
return function(condition, callback) {
|
|
193
246
|
const subChain = $(useTokens);
|
|
@@ -201,6 +254,7 @@ function $(useTokens = true) {
|
|
|
201
254
|
return proxy;
|
|
202
255
|
};
|
|
203
256
|
}
|
|
257
|
+
|
|
204
258
|
if (prop === 'container') {
|
|
205
259
|
return function(condition, callback) {
|
|
206
260
|
const subChain = $(useTokens);
|
|
@@ -214,6 +268,7 @@ function $(useTokens = true) {
|
|
|
214
268
|
return proxy;
|
|
215
269
|
};
|
|
216
270
|
}
|
|
271
|
+
|
|
217
272
|
if (prop === 'layer') {
|
|
218
273
|
return function(name, callback) {
|
|
219
274
|
const subChain = $(useTokens);
|
|
@@ -227,6 +282,7 @@ function $(useTokens = true) {
|
|
|
227
282
|
return proxy;
|
|
228
283
|
};
|
|
229
284
|
}
|
|
285
|
+
|
|
230
286
|
if (prop === 'counterStyle') {
|
|
231
287
|
return function(name, callback) {
|
|
232
288
|
const subChain = $(useTokens);
|
|
@@ -235,12 +291,12 @@ function $(useTokens = true) {
|
|
|
235
291
|
catcher.atRules.push({
|
|
236
292
|
type: 'counter-style',
|
|
237
293
|
name: name,
|
|
238
|
-
properties: properties
|
|
239
|
-
atomic: false
|
|
294
|
+
properties: properties
|
|
240
295
|
});
|
|
241
296
|
return proxy;
|
|
242
297
|
};
|
|
243
298
|
}
|
|
299
|
+
|
|
244
300
|
if (prop === 'property') {
|
|
245
301
|
return function(name, callback) {
|
|
246
302
|
const subChain = $(useTokens);
|
|
@@ -249,90 +305,46 @@ function $(useTokens = true) {
|
|
|
249
305
|
catcher.atRules.push({
|
|
250
306
|
type: 'property',
|
|
251
307
|
name: name,
|
|
252
|
-
descriptors: descriptors
|
|
253
|
-
atomic: false
|
|
308
|
+
descriptors: descriptors
|
|
254
309
|
});
|
|
255
310
|
return proxy;
|
|
256
311
|
};
|
|
257
312
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// Handle percent case: .percent(50, callback)
|
|
261
|
-
if (prop === 'percent') {
|
|
262
|
-
const value = args[0];
|
|
263
|
-
const callback = args[1];
|
|
264
|
-
const subChain = $(useTokens);
|
|
265
|
-
const properties = callback(subChain).block();
|
|
266
|
-
if (!this._keyframeSteps) this._keyframeSteps = {};
|
|
267
|
-
this._keyframeSteps[`${value}%`] = properties;
|
|
268
|
-
return this; // Return this for chaining
|
|
269
|
-
}
|
|
270
|
-
const callback = args[0];
|
|
271
|
-
const subChain = $(useTokens);
|
|
272
|
-
const properties = callback(subChain).block();
|
|
273
|
-
if (!this._keyframeSteps) this._keyframeSteps = {};
|
|
274
|
-
this._keyframeSteps[prop] = properties;
|
|
275
|
-
return this;
|
|
276
|
-
};
|
|
277
|
-
}
|
|
313
|
+
|
|
314
|
+
// Regular CSS properties
|
|
278
315
|
const cssProperty = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
279
316
|
if (validProperties && validProperties.length > 0 && !validProperties.includes(cssProperty)) {
|
|
280
317
|
console.warn(`Warning: '${cssProperty}' may not be a valid CSS property`);
|
|
281
318
|
}
|
|
319
|
+
|
|
282
320
|
return function(value) {
|
|
283
321
|
catcher[prop] = resolveToken(value, useTokens);
|
|
284
322
|
return proxy;
|
|
285
323
|
};
|
|
286
324
|
}
|
|
287
325
|
};
|
|
326
|
+
|
|
288
327
|
const proxy = new Proxy({}, handler);
|
|
328
|
+
|
|
329
|
+
// Async load CSS properties if needed
|
|
289
330
|
if (chain.cachedValidProperties.length === 0) {
|
|
290
331
|
chain.getCSSProperties().catch(err => {
|
|
291
332
|
console.error('Failed to load CSS properties:', err.message);
|
|
292
333
|
});
|
|
293
334
|
}
|
|
335
|
+
|
|
294
336
|
return proxy;
|
|
295
337
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
args.forEach((value) => {
|
|
299
|
-
if (!value) return;
|
|
300
|
-
if (value.selectors) {
|
|
301
|
-
let mainRuleBody = '';
|
|
302
|
-
let atRulesOutput = '';
|
|
303
|
-
for (let key in value) {
|
|
304
|
-
if (key === 'selectors' || !value.hasOwnProperty(key)) continue;
|
|
305
|
-
if (key === 'atRules' && Array.isArray(value[key])) {
|
|
306
|
-
value[key].forEach(rule => {
|
|
307
|
-
atRulesOutput += processAtRule(rule, value.selectors);
|
|
308
|
-
});
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
313
|
-
mainRuleBody += ` ${kebabKey}: ${value[key]};\n`;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
if (mainRuleBody.trim()) {
|
|
317
|
-
cssOutput += `${value.selectors.join(', ')} {\n${mainRuleBody}}\n`;
|
|
318
|
-
}
|
|
319
|
-
cssOutput += atRulesOutput;
|
|
320
|
-
}
|
|
321
|
-
else if (value.type) {
|
|
322
|
-
cssOutput += processStandaloneAtRule(value);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
cssOutput = cssOutput.replace(/\n{3,}/g, '\n\n').trim();
|
|
326
|
-
chain.cssOutput = cssOutput;
|
|
327
|
-
return cssOutput;
|
|
328
|
-
};
|
|
338
|
+
|
|
339
|
+
// Process at-rules
|
|
329
340
|
function processAtRule(rule, parentSelectors = null) {
|
|
330
341
|
let output = '';
|
|
342
|
+
|
|
331
343
|
switch(rule.type) {
|
|
332
344
|
case 'media':
|
|
333
345
|
if (parentSelectors) {
|
|
334
346
|
let mediaBody = '';
|
|
335
|
-
if (rule.styles) {
|
|
347
|
+
if (rule.styles && typeof rule.styles === 'object') {
|
|
336
348
|
for (let prop in rule.styles) {
|
|
337
349
|
if (prop !== 'selectors' && rule.styles.hasOwnProperty(prop)) {
|
|
338
350
|
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
@@ -344,24 +356,52 @@ function processAtRule(rule, parentSelectors = null) {
|
|
|
344
356
|
output = `@media ${rule.query} {\n ${parentSelectors.join(', ')} {\n${mediaBody} }\n}\n`;
|
|
345
357
|
}
|
|
346
358
|
} else {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
355
|
-
ruleBody += ` ${kebabKey}: ${styleObj[prop]};\n`;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
if (ruleBody.trim()) {
|
|
359
|
-
output += ` ${styleObj.selectors.join(', ')} {\n${ruleBody} }\n`;
|
|
359
|
+
output = `@media ${rule.query} {\n`;
|
|
360
|
+
if (rule.styles && rule.styles.selectors) {
|
|
361
|
+
let ruleBody = '';
|
|
362
|
+
for (let prop in rule.styles) {
|
|
363
|
+
if (prop !== 'selectors' && rule.styles.hasOwnProperty(prop)) {
|
|
364
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
365
|
+
ruleBody += ` ${kebabKey}: ${rule.styles[prop]};\n`;
|
|
360
366
|
}
|
|
361
367
|
}
|
|
362
|
-
|
|
368
|
+
if (ruleBody.trim()) {
|
|
369
|
+
output += ` ${rule.styles.selectors.join(', ')} {\n${ruleBody} }\n`;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
output += '}\n';
|
|
373
|
+
}
|
|
374
|
+
break;
|
|
375
|
+
|
|
376
|
+
case 'keyframes':
|
|
377
|
+
output = `@keyframes ${rule.name} {\n`;
|
|
378
|
+
for (let step in rule.steps) {
|
|
379
|
+
output += ` ${step} {\n`;
|
|
380
|
+
for (let prop in rule.steps[step]) {
|
|
381
|
+
if (prop !== 'selectors') {
|
|
382
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
383
|
+
output += ` ${kebabKey}: ${rule.steps[step][prop]};\n`;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
output += ' }\n';
|
|
387
|
+
}
|
|
388
|
+
output += '}\n';
|
|
389
|
+
break;
|
|
390
|
+
|
|
391
|
+
case 'font-face':
|
|
392
|
+
output = '@font-face {\n';
|
|
393
|
+
for (let prop in rule.properties) {
|
|
394
|
+
if (prop !== 'selectors') {
|
|
395
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
396
|
+
output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
|
|
397
|
+
}
|
|
363
398
|
}
|
|
364
|
-
|
|
399
|
+
output += '}\n';
|
|
400
|
+
break;
|
|
401
|
+
|
|
402
|
+
case 'supports':
|
|
403
|
+
output = `@supports ${rule.condition} {\n`;
|
|
404
|
+
if (rule.styles && rule.styles.selectors) {
|
|
365
405
|
let ruleBody = '';
|
|
366
406
|
for (let prop in rule.styles) {
|
|
367
407
|
if (prop !== 'selectors' && rule.styles.hasOwnProperty(prop)) {
|
|
@@ -374,116 +414,513 @@ function processAtRule(rule, parentSelectors = null) {
|
|
|
374
414
|
}
|
|
375
415
|
}
|
|
376
416
|
output += '}\n';
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
case '
|
|
380
|
-
output = `@
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
for (let prop in rule.
|
|
384
|
-
|
|
385
|
-
|
|
417
|
+
break;
|
|
418
|
+
|
|
419
|
+
case 'container':
|
|
420
|
+
output = `@container ${rule.condition} {\n`;
|
|
421
|
+
if (rule.styles && rule.styles.selectors) {
|
|
422
|
+
let ruleBody = '';
|
|
423
|
+
for (let prop in rule.styles) {
|
|
424
|
+
if (prop !== 'selectors' && rule.styles.hasOwnProperty(prop)) {
|
|
425
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
426
|
+
ruleBody += ` ${kebabKey}: ${rule.styles[prop]};\n`;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (ruleBody.trim()) {
|
|
430
|
+
output += ` ${rule.styles.selectors.join(', ')} {\n${ruleBody} }\n`;
|
|
386
431
|
}
|
|
387
|
-
output += ' }\n';
|
|
388
432
|
}
|
|
389
433
|
output += '}\n';
|
|
390
434
|
break;
|
|
391
|
-
|
|
392
|
-
|
|
435
|
+
|
|
436
|
+
case 'layer':
|
|
437
|
+
output = `@layer ${rule.name} {\n`;
|
|
438
|
+
if (rule.styles && rule.styles.selectors) {
|
|
439
|
+
let ruleBody = '';
|
|
440
|
+
for (let prop in rule.styles) {
|
|
441
|
+
if (prop !== 'selectors' && rule.styles.hasOwnProperty(prop)) {
|
|
442
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
443
|
+
ruleBody += ` ${kebabKey}: ${rule.styles[prop]};\n`;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (ruleBody.trim()) {
|
|
447
|
+
output += ` ${rule.styles.selectors.join(', ')} {\n${ruleBody} }\n`;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
output += '}\n';
|
|
451
|
+
break;
|
|
452
|
+
|
|
453
|
+
case 'counter-style':
|
|
454
|
+
output = `@counter-style ${rule.name} {\n`;
|
|
393
455
|
for (let prop in rule.properties) {
|
|
394
|
-
|
|
395
|
-
|
|
456
|
+
if (prop !== 'selectors') {
|
|
457
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
458
|
+
output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
output += '}\n';
|
|
462
|
+
break;
|
|
463
|
+
|
|
464
|
+
case 'property':
|
|
465
|
+
output = `@property ${rule.name} {\n`;
|
|
466
|
+
for (let desc in rule.descriptors) {
|
|
467
|
+
if (desc !== 'selectors') {
|
|
468
|
+
const kebabKey = desc.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
469
|
+
output += ` ${kebabKey}: ${rule.descriptors[desc]};\n`;
|
|
470
|
+
}
|
|
396
471
|
}
|
|
397
472
|
output += '}\n';
|
|
398
473
|
break;
|
|
399
474
|
}
|
|
475
|
+
|
|
400
476
|
return output;
|
|
401
477
|
}
|
|
478
|
+
|
|
402
479
|
function processStandaloneAtRule(rule) {
|
|
403
480
|
let output = '';
|
|
481
|
+
|
|
404
482
|
switch(rule.type) {
|
|
405
483
|
case 'font-face':
|
|
406
484
|
output = '@font-face {\n';
|
|
407
485
|
for (let prop in rule.properties) {
|
|
408
|
-
|
|
409
|
-
|
|
486
|
+
if (prop !== 'selectors') {
|
|
487
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
488
|
+
output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
|
|
489
|
+
}
|
|
410
490
|
}
|
|
411
491
|
output += '}\n';
|
|
412
492
|
break;
|
|
493
|
+
|
|
413
494
|
case 'keyframes':
|
|
414
495
|
output = `@keyframes ${rule.name} {\n`;
|
|
415
496
|
for (let step in rule.steps) {
|
|
416
497
|
output += ` ${step} {\n`;
|
|
417
498
|
for (let prop in rule.steps[step]) {
|
|
418
|
-
|
|
419
|
-
|
|
499
|
+
if (prop !== 'selectors') {
|
|
500
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
501
|
+
output += ` ${kebabKey}: ${rule.steps[step][prop]};\n`;
|
|
502
|
+
}
|
|
420
503
|
}
|
|
421
504
|
output += ' }\n';
|
|
422
505
|
}
|
|
423
506
|
output += '}\n';
|
|
424
507
|
break;
|
|
508
|
+
|
|
425
509
|
case 'counter-style':
|
|
426
510
|
output = `@counter-style ${rule.name} {\n`;
|
|
427
511
|
for (let prop in rule.properties) {
|
|
428
|
-
|
|
429
|
-
|
|
512
|
+
if (prop !== 'selectors') {
|
|
513
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
514
|
+
output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
|
|
515
|
+
}
|
|
430
516
|
}
|
|
431
517
|
output += '}\n';
|
|
432
518
|
break;
|
|
519
|
+
|
|
433
520
|
case 'property':
|
|
434
521
|
output = `@property ${rule.name} {\n`;
|
|
435
522
|
for (let desc in rule.descriptors) {
|
|
436
|
-
|
|
437
|
-
|
|
523
|
+
if (desc !== 'selectors') {
|
|
524
|
+
const kebabKey = desc.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
525
|
+
output += ` ${kebabKey}: ${rule.descriptors[desc]};\n`;
|
|
526
|
+
}
|
|
438
527
|
}
|
|
439
528
|
output += '}\n';
|
|
440
529
|
break;
|
|
441
530
|
}
|
|
531
|
+
|
|
442
532
|
return output;
|
|
443
533
|
}
|
|
534
|
+
|
|
535
|
+
const run = (...args) => {
|
|
536
|
+
let cssOutput = '';
|
|
537
|
+
const styleObjs = [];
|
|
538
|
+
|
|
539
|
+
args.forEach((value) => {
|
|
540
|
+
if (!value) return;
|
|
541
|
+
styleObjs.push(value);
|
|
542
|
+
|
|
543
|
+
if (value.selectors) {
|
|
544
|
+
let mainRuleBody = '';
|
|
545
|
+
let atRulesOutput = '';
|
|
546
|
+
|
|
547
|
+
for (let key in value) {
|
|
548
|
+
if (key === 'selectors' || !value.hasOwnProperty(key)) continue;
|
|
549
|
+
|
|
550
|
+
if (key === 'atRules' && Array.isArray(value[key])) {
|
|
551
|
+
value[key].forEach(rule => {
|
|
552
|
+
atRulesOutput += processAtRule(rule, value.selectors);
|
|
553
|
+
});
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (key === 'hover' && typeof value[key] === 'object') {
|
|
558
|
+
// Handle hover styles
|
|
559
|
+
let hoverBody = '';
|
|
560
|
+
for (let hoverKey in value[key]) {
|
|
561
|
+
const kebabKey = hoverKey.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
562
|
+
hoverBody += ` ${kebabKey}: ${value[key][hoverKey]};\n`;
|
|
563
|
+
}
|
|
564
|
+
if (hoverBody) {
|
|
565
|
+
cssOutput += `${value.selectors.join(', ')}:hover {\n${hoverBody}}\n`;
|
|
566
|
+
}
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
571
|
+
mainRuleBody += ` ${kebabKey}: ${value[key]};\n`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (mainRuleBody.trim()) {
|
|
575
|
+
cssOutput += `${value.selectors.join(', ')} {\n${mainRuleBody}}\n`;
|
|
576
|
+
}
|
|
577
|
+
cssOutput += atRulesOutput;
|
|
578
|
+
|
|
579
|
+
} else if (value.type) {
|
|
580
|
+
cssOutput += processStandaloneAtRule(value);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
cssOutput = cssOutput.replace(/\n{3,}/g, '\n\n').trim();
|
|
585
|
+
chain.cssOutput = cssOutput;
|
|
586
|
+
|
|
587
|
+
if (atomicOptimizer.options.enabled) {
|
|
588
|
+
const { css, map, stats } = atomicOptimizer.optimize(styleObjs);
|
|
589
|
+
chain.cssOutput = css;
|
|
590
|
+
chain.classMap = map;
|
|
591
|
+
chain.atomicStats = stats;
|
|
592
|
+
return css;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return cssOutput;
|
|
596
|
+
};
|
|
597
|
+
|
|
444
598
|
const compile = (obj) => {
|
|
445
599
|
let cssString = '';
|
|
600
|
+
const collected = [];
|
|
601
|
+
|
|
446
602
|
for (const key in obj) {
|
|
447
|
-
if (obj.hasOwnProperty(key))
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
603
|
+
if (!obj.hasOwnProperty(key)) continue;
|
|
604
|
+
const element = obj[key];
|
|
605
|
+
|
|
606
|
+
if (element.atRules && Array.isArray(element.atRules)) {
|
|
607
|
+
element.atRules.forEach(rule => {
|
|
608
|
+
cssString += processAtRule(rule, null);
|
|
609
|
+
});
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (element.selectors) {
|
|
614
|
+
collected.push(element);
|
|
615
|
+
let elementCSS = '';
|
|
616
|
+
let atRulesCSS = '';
|
|
617
|
+
|
|
618
|
+
for (let prop in element) {
|
|
619
|
+
if (prop === 'selectors' || !element.hasOwnProperty(prop)) continue;
|
|
620
|
+
|
|
621
|
+
if (prop === 'atRules' && Array.isArray(element[prop])) {
|
|
622
|
+
element[prop].forEach(rule => {
|
|
623
|
+
atRulesCSS += processAtRule(rule, element.selectors);
|
|
624
|
+
});
|
|
625
|
+
} else if (prop === 'hover' && typeof element[prop] === 'object') {
|
|
626
|
+
let hoverBody = '';
|
|
627
|
+
for (let hoverKey in element[prop]) {
|
|
628
|
+
const kebabKey = hoverKey.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
629
|
+
hoverBody += ` ${kebabKey}: ${element[prop][hoverKey]};\n`;
|
|
467
630
|
}
|
|
631
|
+
if (hoverBody) {
|
|
632
|
+
cssString += `${element.selectors.join(', ')}:hover {\n${hoverBody}}\n`;
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
636
|
+
elementCSS += ` ${kebabKey}: ${element[prop]};\n`;
|
|
468
637
|
}
|
|
469
|
-
if (elementCSS.trim()) {
|
|
470
|
-
cssString += `${element.selectors.join(', ')} {\n${elementCSS}}\n`;
|
|
471
|
-
}
|
|
472
|
-
cssString += atRulesCSS;
|
|
473
638
|
}
|
|
474
|
-
|
|
475
|
-
|
|
639
|
+
|
|
640
|
+
if (elementCSS.trim()) {
|
|
641
|
+
cssString += `${element.selectors.join(', ')} {\n${elementCSS}}\n`;
|
|
476
642
|
}
|
|
643
|
+
cssString += atRulesCSS;
|
|
477
644
|
}
|
|
478
645
|
}
|
|
646
|
+
|
|
479
647
|
chain.cssOutput = cssString.trim();
|
|
648
|
+
|
|
649
|
+
if (atomicOptimizer.options.enabled) {
|
|
650
|
+
const { css, map, stats } = atomicOptimizer.optimize(collected);
|
|
651
|
+
chain.cssOutput = css;
|
|
652
|
+
chain.classMap = map;
|
|
653
|
+
chain.atomicStats = stats;
|
|
654
|
+
return css;
|
|
655
|
+
}
|
|
656
|
+
|
|
480
657
|
return chain.cssOutput;
|
|
481
658
|
};
|
|
659
|
+
|
|
660
|
+
function recipe(options) {
|
|
661
|
+
const {
|
|
662
|
+
base,
|
|
663
|
+
variants = {},
|
|
664
|
+
defaultVariants = {},
|
|
665
|
+
compoundVariants = []
|
|
666
|
+
} = options;
|
|
667
|
+
|
|
668
|
+
// Store the original style objects
|
|
669
|
+
const baseStyle = typeof base === 'function' ? base() : base;
|
|
670
|
+
const variantStyles = {};
|
|
671
|
+
|
|
672
|
+
// Store variant style objects
|
|
673
|
+
for (const [variantName, variantMap] of Object.entries(variants)) {
|
|
674
|
+
variantStyles[variantName] = {};
|
|
675
|
+
for (const [variantKey, variantStyle] of Object.entries(variantMap)) {
|
|
676
|
+
variantStyles[variantName][variantKey] = typeof variantStyle === 'function'
|
|
677
|
+
? variantStyle()
|
|
678
|
+
: variantStyle;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Store compound variant styles
|
|
683
|
+
const compoundStyles = compoundVariants.map(cv => ({
|
|
684
|
+
condition: cv.variants || cv,
|
|
685
|
+
style: typeof cv.style === 'function' ? cv.style() : cv.style
|
|
686
|
+
}));
|
|
687
|
+
|
|
688
|
+
// Helper to extract atomic class names from a style object
|
|
689
|
+
function getAtomicClasses(styleObj) {
|
|
690
|
+
if (!styleObj) return [];
|
|
691
|
+
|
|
692
|
+
const classes = [];
|
|
693
|
+
|
|
694
|
+
// Check if atomic optimizer is enabled and has class mapping
|
|
695
|
+
if (atomicOptimizer.options.enabled && chain.classMap) {
|
|
696
|
+
// Generate a temporary style to get atomic classes
|
|
697
|
+
const tempBuilder = $(true);
|
|
698
|
+
for (const [prop, value] of Object.entries(styleObj)) {
|
|
699
|
+
if (prop !== 'selectors' && prop !== 'hover' && tempBuilder[prop]) {
|
|
700
|
+
tempBuilder[prop](value);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Add hover styles if present
|
|
705
|
+
if (styleObj.hover) {
|
|
706
|
+
tempBuilder.hover();
|
|
707
|
+
for (const [hoverProp, hoverValue] of Object.entries(styleObj.hover)) {
|
|
708
|
+
if (tempBuilder[hoverProp]) tempBuilder[hoverProp](hoverValue);
|
|
709
|
+
}
|
|
710
|
+
tempBuilder.end();
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Get the style object to extract atomic classes
|
|
714
|
+
const style = tempBuilder.block();
|
|
715
|
+
|
|
716
|
+
// Find matching atomic classes from the classMap
|
|
717
|
+
// This requires that the atomic optimizer has processed these styles
|
|
718
|
+
const selectorKey = JSON.stringify(style);
|
|
719
|
+
if (chain.classMap[selectorKey]) {
|
|
720
|
+
return chain.classMap[selectorKey].split(' ');
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return classes;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Helper to merge style objects and return class names
|
|
728
|
+
function mergeStylesToClasses(...styles) {
|
|
729
|
+
const merged = {};
|
|
730
|
+
for (const style of styles) {
|
|
731
|
+
if (!style) continue;
|
|
732
|
+
for (const [key, value] of Object.entries(style)) {
|
|
733
|
+
if (key === 'selectors') {
|
|
734
|
+
merged.selectors = merged.selectors || [];
|
|
735
|
+
merged.selectors.push(...(Array.isArray(value) ? value : [value]));
|
|
736
|
+
} else if (key === 'hover' && typeof value === 'object') {
|
|
737
|
+
if (!merged.hover) merged.hover = {};
|
|
738
|
+
Object.assign(merged.hover, value);
|
|
739
|
+
} else if (key !== 'selectors') {
|
|
740
|
+
merged[key] = value;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Generate a unique class name for this combination
|
|
746
|
+
const selectorKey = Object.entries(merged)
|
|
747
|
+
.filter(([k]) => k !== 'selectors' && k !== 'hover')
|
|
748
|
+
.map(([k, v]) => `${k}-${v}`)
|
|
749
|
+
.join('--');
|
|
750
|
+
|
|
751
|
+
const baseClassName = `recipe-${selectorKey}`;
|
|
752
|
+
|
|
753
|
+
// Register this style with the atomic optimizer if enabled
|
|
754
|
+
if (atomicOptimizer.options.enabled) {
|
|
755
|
+
// Create a style object with the merged styles
|
|
756
|
+
const styleObj = {
|
|
757
|
+
selectors: [`.${baseClassName}`],
|
|
758
|
+
...merged
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
// Process through atomic optimizer
|
|
762
|
+
const { css, map } = atomicOptimizer.optimize({ [baseClassName]: styleObj });
|
|
763
|
+
|
|
764
|
+
// Store the generated CSS in chain
|
|
765
|
+
if (css) {
|
|
766
|
+
chain.cssOutput = (chain.cssOutput || '') + css;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Return atomic classes if available, otherwise return the generated class
|
|
770
|
+
if (map && map[`.${baseClassName}`]) {
|
|
771
|
+
return map[`.${baseClassName}`].split(' ');
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Fallback: return the generated class name
|
|
776
|
+
return [baseClassName];
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// The main pick function that returns class names
|
|
780
|
+
function pick(variantSelection = {}) {
|
|
781
|
+
// Merge defaults with selection
|
|
782
|
+
const selected = { ...defaultVariants, ...variantSelection };
|
|
783
|
+
|
|
784
|
+
// Collect all relevant styles
|
|
785
|
+
const stylesToMerge = [];
|
|
786
|
+
|
|
787
|
+
// Add base style
|
|
788
|
+
if (baseStyle) stylesToMerge.push(baseStyle);
|
|
789
|
+
|
|
790
|
+
// Add variant styles
|
|
791
|
+
for (const [variantName, variantValue] of Object.entries(selected)) {
|
|
792
|
+
const variantStyle = variantStyles[variantName]?.[variantValue];
|
|
793
|
+
if (variantStyle) stylesToMerge.push(variantStyle);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Add compound variants
|
|
797
|
+
for (const cv of compoundStyles) {
|
|
798
|
+
const matches = Object.entries(cv.condition).every(
|
|
799
|
+
([key, value]) => selected[key] === value
|
|
800
|
+
);
|
|
801
|
+
if (matches && cv.style) stylesToMerge.push(cv.style);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Merge styles and return class names
|
|
805
|
+
const classNames = mergeStylesToClasses(...stylesToMerge);
|
|
806
|
+
|
|
807
|
+
// Return as string for easy use
|
|
808
|
+
return classNames.join(' ');
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Add metadata for introspection
|
|
812
|
+
pick.variants = variants;
|
|
813
|
+
pick.defaultVariants = defaultVariants;
|
|
814
|
+
pick.base = baseStyle;
|
|
815
|
+
|
|
816
|
+
// Helper to get all possible variant combinations
|
|
817
|
+
pick.getAllVariants = () => {
|
|
818
|
+
const result = [];
|
|
819
|
+
const variantKeys = Object.keys(variants);
|
|
820
|
+
|
|
821
|
+
function generate(current, index) {
|
|
822
|
+
if (index === variantKeys.length) {
|
|
823
|
+
result.push({ ...current });
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const variantName = variantKeys[index];
|
|
827
|
+
for (const variantValue of Object.keys(variants[variantName])) {
|
|
828
|
+
current[variantName] = variantValue;
|
|
829
|
+
generate(current, index + 1);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
generate({}, 0);
|
|
834
|
+
return result;
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
// Pre-compile all variants at build time
|
|
838
|
+
pick.compileAll = () => {
|
|
839
|
+
const allVariants = pick.getAllVariants();
|
|
840
|
+
const styles = [];
|
|
841
|
+
|
|
842
|
+
// Add base
|
|
843
|
+
if (baseStyle) styles.push(baseStyle);
|
|
844
|
+
|
|
845
|
+
// Add all variant styles
|
|
846
|
+
for (const variantMap of Object.values(variants)) {
|
|
847
|
+
for (const variantStyle of Object.values(variantMap)) {
|
|
848
|
+
if (variantStyle) styles.push(variantStyle);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Add compound variant styles
|
|
853
|
+
for (const cv of compoundStyles) {
|
|
854
|
+
if (cv.style) styles.push(cv.style);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Compile all styles through atomic optimizer
|
|
858
|
+
if (atomicOptimizer.options.enabled) {
|
|
859
|
+
const styleObj = {};
|
|
860
|
+
styles.forEach((style, i) => {
|
|
861
|
+
const selectors = style.selectors || [`variant-${i}`];
|
|
862
|
+
styleObj[selectors[0].replace(/^\./, '')] = style;
|
|
863
|
+
});
|
|
864
|
+
const { css, map } = atomicOptimizer.optimize(styleObj);
|
|
865
|
+
chain.cssOutput = (chain.cssOutput || '') + css;
|
|
866
|
+
chain.classMap = { ...chain.classMap, ...map };
|
|
867
|
+
return css;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Fallback: use run()
|
|
871
|
+
return run(...styles);
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
// Helper to get CSS for a specific variant combination
|
|
875
|
+
pick.getCSS = (variantSelection = {}) => {
|
|
876
|
+
const selected = { ...defaultVariants, ...variantSelection };
|
|
877
|
+
const stylesToMerge = [];
|
|
878
|
+
|
|
879
|
+
if (baseStyle) stylesToMerge.push(baseStyle);
|
|
880
|
+
for (const [variantName, variantValue] of Object.entries(selected)) {
|
|
881
|
+
const variantStyle = variantStyles[variantName]?.[variantValue];
|
|
882
|
+
if (variantStyle) stylesToMerge.push(variantStyle);
|
|
883
|
+
}
|
|
884
|
+
for (const cv of compoundStyles) {
|
|
885
|
+
const matches = Object.entries(cv.condition).every(
|
|
886
|
+
([key, value]) => selected[key] === value
|
|
887
|
+
);
|
|
888
|
+
if (matches && cv.style) stylesToMerge.push(cv.style);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Create a temporary style object to generate CSS
|
|
892
|
+
const tempBuilder = $(true);
|
|
893
|
+
for (const style of stylesToMerge) {
|
|
894
|
+
for (const [prop, value] of Object.entries(style)) {
|
|
895
|
+
if (prop !== 'selectors' && prop !== 'hover' && tempBuilder[prop]) {
|
|
896
|
+
tempBuilder[prop](value);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (style.hover) {
|
|
900
|
+
tempBuilder.hover();
|
|
901
|
+
for (const [hoverProp, hoverValue] of Object.entries(style.hover)) {
|
|
902
|
+
if (tempBuilder[hoverProp]) tempBuilder[hoverProp](hoverValue);
|
|
903
|
+
}
|
|
904
|
+
tempBuilder.end();
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const mergedStyle = tempBuilder.block();
|
|
909
|
+
const css = compile({ [selected.join('-')]: mergedStyle });
|
|
910
|
+
return css;
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
return pick;
|
|
914
|
+
}
|
|
915
|
+
|
|
482
916
|
module.exports = {
|
|
483
917
|
chain,
|
|
484
918
|
$,
|
|
485
919
|
run,
|
|
486
920
|
compile,
|
|
487
921
|
createTokens,
|
|
488
|
-
responsive
|
|
922
|
+
responsive,
|
|
923
|
+
configureAtomic,
|
|
924
|
+
atomicOptimizer,
|
|
925
|
+
recipe
|
|
489
926
|
};
|