@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/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('⚠️ CSS properties not cached, will load on first use');
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
- if (prop === '$') {
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; // Return the proxy for chaining
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: Array.isArray(result) ? result : [result]
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' || stepProp === 'percent') {
146
- return function(...args) {
147
- if (stepProp === 'percent') {
148
- const value = args[0];
149
- const stepCallback = args[1];
150
- const subChain = $(useTokens);
151
- const properties = stepCallback(subChain).block();
152
- target._keyframeSteps[`${value}%`] = properties;
153
- } else {
154
- const stepCallback = args[0];
155
- const subChain = $(useTokens);
156
- const properties = stepCallback(subChain).block();
157
- target._keyframeSteps[stepProp] = properties;
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
- if (prop === 'from' || prop === 'to' || prop === 'percent') {
259
- return function(...args) {
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
- const run = (...args) => {
297
- let cssOutput = '';
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
- output = `@media ${rule.query} {\n`;
348
- if (Array.isArray(rule.styles)) {
349
- rule.styles.forEach(styleObj => {
350
- if (styleObj.selectors) {
351
- let ruleBody = '';
352
- for (let prop in styleObj) {
353
- if (prop !== 'selectors' && styleObj.hasOwnProperty(prop)) {
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
- else if (rule.styles && rule.styles.selectors) {
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
- break;
379
- case 'keyframes':
380
- output = `@keyframes ${rule.name} {\n`;
381
- for (let step in rule.steps) {
382
- output += ` ${step} {\n`;
383
- for (let prop in rule.steps[step]) {
384
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
385
- output += ` ${kebabKey}: ${rule.steps[step][prop]};\n`;
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
- case 'font-face':
392
- output = '@font-face {\n';
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
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
395
- output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
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
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
409
- output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
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
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
419
- output += ` ${kebabKey}: ${rule.steps[step][prop]};\n`;
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
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
429
- output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
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
- const kebabKey = desc.replace(/([A-Z])/g, '-$1').toLowerCase();
437
- output += ` ${kebabKey}: ${rule.descriptors[desc]};\n`;
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
- const element = obj[key];
449
- if (element.atRules && Array.isArray(element.atRules)) {
450
- element.atRules.forEach(rule => {
451
- cssString += processAtRule(rule, null);
452
- });
453
- continue;
454
- }
455
- if (element.selectors) {
456
- let elementCSS = '';
457
- let atRulesCSS = '';
458
- for (let prop in element) {
459
- if (prop === 'selectors' || !element.hasOwnProperty(prop)) continue;
460
- if (prop === 'atRules' && Array.isArray(element[prop])) {
461
- element[prop].forEach(rule => {
462
- atRulesCSS += processAtRule(rule, element.selectors);
463
- });
464
- } else {
465
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
466
- elementCSS += ` ${kebabKey}: ${element[prop]};\n`;
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
- else {
475
- console.log(`Warning: Unknown element type for key "${key}":`, element);
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
  };