chaincss 2.1.30 → 2.1.31

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.
@@ -1,3 +1,9 @@
1
+ // src/compiler/btt.ts
2
+ /**
3
+ * ChainCSS Build-Time Compiler
4
+ * Core compilation, AT-rules, CSS property loading, source maps
5
+ */
6
+
1
7
  import fs from 'fs';
2
8
  import path from 'path';
3
9
  import https from 'https';
@@ -13,377 +19,47 @@ import { animationPresets, createAnimation } from './animations.js';
13
19
  import { helpers } from './helpers.js';
14
20
  import type { AnimationConfig } from './animations.js';
15
21
  import { chain, setTokenContext } from './Chain.js';
22
+ import { takeSnapshot as ts, isTimelineEnabled as timelineActive } from './timeline.js';
16
23
 
17
24
  // ============================================================================
18
- // Re-export Chain API from Chain.ts
25
+ // Re-exports from split modules
19
26
  // ============================================================================
20
27
  export { setBreakpoints } from './breakpoints.js';
21
28
  export { chain, enableDebug } from './Chain.js';
22
29
 
23
- // ============================================================================
24
- // Style Timeline / Diff Viewer
25
- // ============================================================================
26
-
27
- interface StyleSnapshot {
28
- id: string;
29
- timestamp: number;
30
- selector: string;
31
- styles: Record<string, any>;
32
- source: string;
33
- hash: string;
34
- }
35
-
36
- interface StyleChange {
37
- id: string;
38
- timestamp: number;
39
- selector: string;
40
- property: string;
41
- oldValue: any;
42
- newValue: any;
43
- type: 'add' | 'remove' | 'modify';
44
- }
45
-
46
- let styleHistory: StyleSnapshot[] = [];
47
- let styleChanges: StyleChange[] = [];
48
- let timelineEnabled = false;
49
- let currentSnapshotId = 0;
50
-
51
- // Enable/disable timeline tracking
52
- export function enableTimeline(enable: boolean = true): void {
53
- timelineEnabled = enable;
54
- if (!enable) {
55
- styleHistory = [];
56
- styleChanges = [];
57
- }
58
- }
59
-
60
- export function getStyleHistory(): StyleSnapshot[] {
61
- return styleHistory;
62
- }
63
-
64
- export function getStyleChanges(): StyleChange[] {
65
- return styleChanges;
66
- }
67
-
68
- export function getStyleDiff(snapshotId1: string, snapshotId2: string): Record<string, any> {
69
- const snapshot1 = styleHistory.find(s => s.id === snapshotId1);
70
- const snapshot2 = styleHistory.find(s => s.id === snapshotId2);
71
-
72
- if (!snapshot1 || !snapshot2) {
73
- return { error: 'Snapshot not found' };
74
- }
75
-
76
- const diff: Record<string, any> = {
77
- added: {},
78
- removed: {},
79
- modified: {}
80
- };
81
-
82
- // Find added and modified properties
83
- for (const [key, value] of Object.entries(snapshot2.styles)) {
84
- if (!(key in snapshot1.styles)) {
85
- diff.added[key] = value;
86
- } else if (JSON.stringify(snapshot1.styles[key]) !== JSON.stringify(value)) {
87
- diff.modified[key] = {
88
- old: snapshot1.styles[key],
89
- new: value
90
- };
91
- }
92
- }
93
-
94
- // Find removed properties
95
- for (const [key, value] of Object.entries(snapshot1.styles)) {
96
- if (!(key in snapshot2.styles)) {
97
- diff.removed[key] = value;
98
- }
99
- }
100
-
101
- return diff;
102
- }
103
-
104
- function takeSnapshot(selector: string, styles: Record<string, any>, source: string): string {
105
- if (!timelineEnabled) return '';
106
-
107
- const hash = JSON.stringify(styles);
108
- const existing = styleHistory.find(s => s.selector === selector && s.hash === hash);
109
- if (existing) return existing.id;
110
-
111
- const id = `snapshot_${currentSnapshotId++}`;
112
- const snapshot: StyleSnapshot = {
113
- id,
114
- timestamp: Date.now(),
115
- selector,
116
- styles: { ...styles },
117
- source,
118
- hash
119
- };
120
-
121
- styleHistory.push(snapshot);
122
-
123
- const previousSnapshot = styleHistory.slice(-2)[0];
124
- if (previousSnapshot && previousSnapshot.selector === selector) {
125
- for (const [key, value] of Object.entries(styles)) {
126
- const oldValue = previousSnapshot.styles[key];
127
- if (!(key in previousSnapshot.styles)) {
128
- styleChanges.push({
129
- id: `change_${Date.now()}_${Math.random()}`,
130
- timestamp: Date.now(),
131
- selector,
132
- property: key,
133
- oldValue: undefined,
134
- newValue: value,
135
- type: 'add'
136
- });
137
- } else if (JSON.stringify(oldValue) !== JSON.stringify(value)) {
138
- styleChanges.push({
139
- id: `change_${Date.now()}_${Math.random()}`,
140
- timestamp: Date.now(),
141
- selector,
142
- property: key,
143
- oldValue,
144
- newValue: value,
145
- type: 'modify'
146
- });
147
- }
148
- }
149
-
150
- for (const [key] of Object.entries(previousSnapshot.styles)) {
151
- if (!(key in styles)) {
152
- styleChanges.push({
153
- id: `change_${Date.now()}_${Math.random()}`,
154
- timestamp: Date.now(),
155
- selector,
156
- property: key,
157
- oldValue: previousSnapshot.styles[key],
158
- newValue: undefined,
159
- type: 'remove'
160
- });
161
- }
162
- }
163
- }
164
-
165
- return id;
166
- }
167
-
168
- export function exportTimeline(): string {
169
- return JSON.stringify({
170
- history: styleHistory,
171
- changes: styleChanges,
172
- exportedAt: Date.now()
173
- }, null, 2);
174
- }
175
-
176
- export function clearTimeline(): void {
177
- styleHistory = [];
178
- styleChanges = [];
179
- currentSnapshotId = 0;
180
- }
181
-
182
- // ============================================================================
183
- // Framework Component Generators
184
- // ============================================================================
185
-
186
- interface ComponentInfo {
187
- name: string;
188
- selector: string;
189
- styles: Record<string, any>;
190
- propsDefinition?: Record<string, any>;
191
- framework: 'react' | 'vue' | 'svelte' | 'solid' | 'auto';
192
- }
193
-
194
- function detectFrameworkFromProject(): 'react' | 'vue' | 'svelte' | 'solid' {
195
- try {
196
- require.resolve('react/package.json');
197
- return 'react';
198
- } catch (e) {}
199
- try {
200
- require.resolve('vue/package.json');
201
- return 'vue';
202
- } catch (e) {}
203
- try {
204
- require.resolve('svelte/package.json');
205
- return 'svelte';
206
- } catch (e) {}
207
- try {
208
- require.resolve('solid-js/package.json');
209
- return 'solid';
210
- } catch (e) {}
211
- return 'react';
212
- }
213
-
214
- function generateReactComponent(info: ComponentInfo): string {
215
- const propsInterface = info.propsDefinition
216
- ? Object.entries(info.propsDefinition)
217
- .map(([key, type]) => ` ${key}?: ${type};`)
218
- .join('\n')
219
- : ' [key: string]: any;';
220
-
221
- return `// Auto-generated by ChainCSS
222
- import React from 'react';
223
- import styles from './${info.name}.class.js';
224
- import './${info.name}.css';
225
-
226
- export interface ${info.name}Props {
227
- className?: string;
228
- children?: React.ReactNode;
229
- ${propsInterface}
230
- }
231
-
232
- export const ${info.name}: React.FC<${info.name}Props> = ({
233
- className,
234
- children,
235
- ...props
236
- }) => {
237
- const combinedClassName = [styles.${info.selector.replace(/^\./, '')}, className]
238
- .filter(Boolean)
239
- .join(' ');
240
-
241
- return (
242
- <div className={combinedClassName} {...props}>
243
- {children}
244
- </div>
245
- );
246
- };
247
-
248
- ${info.name}.displayName = 'ChainCSS${info.name}';
249
-
250
- export default ${info.name};
251
- `;
252
- }
253
-
254
- function generateVueComponent(info: ComponentInfo): string {
255
- const propsDefinition = info.propsDefinition
256
- ? Object.entries(info.propsDefinition)
257
- .map(([key, type]) => ` ${key}: { type: ${type}, default: null },`)
258
- .join('\n')
259
- : '';
260
-
261
- return `<!-- Auto-generated by ChainCSS -->
262
- <template>
263
- <component
264
- :is="tag"
265
- :class="combinedClass"
266
- v-bind="$attrs"
267
- >
268
- <slot />
269
- </component>
270
- </template>
271
-
272
- <script>
273
- import styles from './${info.name}.class.js';
274
- import './${info.name}.css';
275
-
276
- export default {
277
- name: 'ChainCSS${info.name}',
278
- props: {
279
- tag: {
280
- type: String,
281
- default: 'div'
282
- },
283
- className: {
284
- type: String,
285
- default: ''
286
- },
287
- ${propsDefinition}
288
- },
289
- computed: {
290
- combinedClass() {
291
- return [styles.${info.selector.replace(/^\./, '')}, this.className]
292
- .filter(Boolean)
293
- .join(' ');
294
- }
295
- }
296
- };
297
- </script>
298
- `;
299
- }
300
-
301
- function generateSvelteComponent(info: ComponentInfo): string {
302
- return `<!-- Auto-generated by ChainCSS -->
303
- <script>
304
- import styles from './${info.name}.class.js';
305
- import './${info.name}.css';
306
-
307
- export let className = '';
308
- export let tag = 'div';
309
-
310
- $: combinedClass = [styles.${info.selector.replace(/^\./, '')}, className]
311
- .filter(Boolean)
312
- .join(' ');
313
- </script>
314
-
315
- <svelte:element this={tag} class={combinedClass}>
316
- <slot />
317
- </svelte:element>
318
- `;
319
- }
30
+ // Timeline
31
+ export {
32
+ enableTimeline, getStyleHistory, getStyleChanges,
33
+ getStyleDiff, exportTimeline, clearTimeline,
34
+ takeSnapshot, isTimelineEnabled
35
+ } from './timeline.js';
36
+ export type { StyleSnapshot, StyleChange } from './timeline.js';
320
37
 
321
- function generateSolidComponent(info: ComponentInfo): string {
322
- return `// Auto-generated by ChainCSS
323
- import { splitProps } from 'solid-js';
324
- import styles from './${info.name}.class.js';
325
- import './${info.name}.css';
38
+ // Scanner
39
+ export { scanContent, scanFileForStyles } from './scanner.js';
326
40
 
327
- export function ${info.name}(props) {
328
- const [local, others] = splitProps(props, ['class', 'children']);
329
- const combinedClass = () => [styles.${info.selector.replace(/^\./, '')}, local.class]
330
- .filter(Boolean)
331
- .join(' ');
332
-
333
- return (
334
- <div class={combinedClass()} {...others}>
335
- {local.children}
336
- </div>
337
- );
338
- }
339
- `;
340
- }
41
+ // Recipe System
42
+ export { recipe } from './recipe.js';
43
+ export type { RecipeOptions, Recipe } from './recipe.js';
341
44
 
342
- // Main function to generate component code
343
- export function generateComponentCode(info: ComponentInfo): string {
344
- let framework = info.framework;
345
- if (framework === 'auto') {
346
- framework = detectFrameworkFromProject();
347
- }
348
-
349
- switch (framework) {
350
- case 'react':
351
- return generateReactComponent(info);
352
- case 'vue':
353
- return generateVueComponent(info);
354
- case 'svelte':
355
- return generateSvelteComponent(info);
356
- case 'solid':
357
- return generateSolidComponent(info);
358
- default:
359
- return generateReactComponent(info);
360
- }
361
- }
45
+ // Component Generator
46
+ export { generateComponentCode, detectFramework } from './component-generator.js';
47
+ export type { ComponentInfo } from './component-generator.js';
362
48
 
363
49
  // ============================================================================
364
- // Style Versioning / Source Maps
50
+ // Source Maps
365
51
  // ============================================================================
366
52
 
367
53
  let enableSourceComments = true;
368
54
 
369
55
  function getSourceLocation(): string | null {
370
56
  if (!enableSourceComments) return null;
371
-
372
57
  const stack = new Error().stack;
373
58
  if (!stack) return null;
374
-
375
- const stackLines = stack.split('\n');
376
-
377
- for (let i = 0; i < stackLines.length; i++) {
378
- const line = stackLines[i];
59
+ for (const line of stack.split('\n')) {
379
60
  const match = line.match(/([^/]+\.chain\.js):(\d+):\d+/);
380
- if (match) {
381
- const fileName = match[1];
382
- const lineNumber = match[2];
383
- return `${fileName}:${lineNumber}`;
384
- }
61
+ if (match) return `${match[1]}:${match[2]}`;
385
62
  }
386
-
387
63
  return null;
388
64
  }
389
65
 
@@ -402,40 +78,24 @@ function addSourceComment(css: string, sourceLocation: string | null): string {
402
78
 
403
79
  const fetchWithHttps = (url: string): Promise<any> => {
404
80
  return new Promise((resolve, reject) => {
405
- const timeout = setTimeout(() => {
406
- req.destroy();
407
- reject(new Error('Request timeout'));
408
- }, 3000);
409
-
81
+ const timeout = setTimeout(() => { req.destroy(); reject(new Error('Request timeout')); }, 3000);
410
82
  const req = https.get(url, (response) => {
411
83
  clearTimeout(timeout);
412
84
  let data = '';
413
85
  response.on('data', (chunk: string) => data += chunk);
414
86
  response.on('end', () => {
415
- try {
416
- resolve(JSON.parse(data));
417
- } catch (error) {
418
- reject(error);
419
- }
87
+ try { resolve(JSON.parse(data)); } catch (error) { reject(error); }
420
88
  });
421
89
  });
422
-
423
- req.on('error', (error) => {
424
- clearTimeout(timeout);
425
- reject(error);
426
- });
90
+ req.on('error', (error) => { clearTimeout(timeout); reject(error); });
427
91
  });
428
92
  };
429
93
 
430
94
  const loadCSSProperties = async (): Promise<string[]> => {
431
- if (chains.cachedValidProperties !== null && chains.cachedValidProperties.length > 0) {
432
- return chains.cachedValidProperties;
433
- }
434
-
95
+ if (chains.cachedValidProperties?.length > 0) return chains.cachedValidProperties;
435
96
  try {
436
97
  const url = 'https://raw.githubusercontent.com/mdn/data/main/css/properties.json';
437
98
  let data: any;
438
-
439
99
  if (typeof fetch !== 'undefined') {
440
100
  const controller = new AbortController();
441
101
  const timeoutId = setTimeout(() => controller.abort(), 3000);
@@ -445,26 +105,17 @@ const loadCSSProperties = async (): Promise<string[]> => {
445
105
  } else {
446
106
  data = await fetchWithHttps(url);
447
107
  }
448
-
449
108
  const allProperties = Object.keys(data);
450
- const baseProperties = new Set<string>();
451
-
452
- allProperties.forEach(prop => {
453
- const baseProp = prop.replace(/^-(webkit|moz|ms|o)-/, '');
454
- baseProperties.add(baseProp);
455
- });
456
-
457
- chains.cachedValidProperties = Array.from(baseProperties).sort();
109
+ chains.cachedValidProperties = allProperties.map(p => p.replace(/^-(webkit|moz|ms|o)-/, '')).filter((v, i, a) => a.indexOf(v) === i).sort();
458
110
  return chains.cachedValidProperties;
459
-
460
- } catch (error) {
111
+ } catch {
461
112
  chains.cachedValidProperties = COMMON_CSS_PROPERTIES;
462
113
  return chains.cachedValidProperties;
463
114
  }
464
115
  };
465
116
 
466
117
  // ============================================================================
467
- // Chain Object & Properties
118
+ // Chain Object
468
119
  // ============================================================================
469
120
 
470
121
  export interface ChainObject {
@@ -472,7 +123,6 @@ export interface ChainObject {
472
123
  cachedValidProperties: string[];
473
124
  classMap: Record<string, string>;
474
125
  atomicStats: any;
475
- componentMap?: Map<string, any>;
476
126
  initializeProperties: () => Promise<void>;
477
127
  getCachedProperties: () => string[] | null;
478
128
  }
@@ -482,30 +132,17 @@ export const chains: ChainObject = {
482
132
  cachedValidProperties: [],
483
133
  classMap: {},
484
134
  atomicStats: null,
485
-
486
135
  async initializeProperties() {
487
- if (this.cachedValidProperties && this.cachedValidProperties.length > 0) {
488
- return;
489
- }
490
- const properties = await loadCSSProperties();
491
- this.cachedValidProperties = properties;
136
+ if (this.cachedValidProperties?.length > 0) return;
137
+ this.cachedValidProperties = await loadCSSProperties();
492
138
  },
493
-
494
- getCachedProperties() {
495
- return this.cachedValidProperties;
496
- }
139
+ getCachedProperties() { return this.cachedValidProperties; }
497
140
  };
498
141
 
499
142
  let atomicOptimizer: AtomicOptimizer | null = null;
500
-
501
- export function setAtomicOptimizer(optimizer: AtomicOptimizer | null): void {
502
- atomicOptimizer = optimizer;
503
- }
504
-
143
+ export function setAtomicOptimizer(optimizer: AtomicOptimizer | null): void { atomicOptimizer = optimizer; }
505
144
  export function configureAtomic(opts: Record<string, any>): void {
506
- if (atomicOptimizer) {
507
- Object.assign(atomicOptimizer.options, opts);
508
- }
145
+ if (atomicOptimizer) Object.assign(atomicOptimizer.options, opts);
509
146
  }
510
147
 
511
148
  // ============================================================================
@@ -513,40 +150,25 @@ export function configureAtomic(opts: Record<string, any>): void {
513
150
  // ============================================================================
514
151
 
515
152
  export const tokens = originalToken;
516
-
517
153
  export function createTokens(tokenValues: Record<string, any>): DesignTokens {
518
154
  const tokenObj = new DesignTokens(tokenValues);
519
- // Also set the token context in Chain.ts
520
155
  setTokenContext(tokenObj);
521
156
  return tokenObj;
522
157
  }
523
158
 
524
159
  // ============================================================================
525
- // AT-Rule Processing
160
+ // Types
526
161
  // ============================================================================
527
162
 
528
163
  export interface AtRule {
529
164
  type: 'media' | 'keyframes' | 'font-face' | 'supports' | 'container' | 'layer' | 'counter-style' | 'property';
530
- query?: string;
531
- condition?: string;
532
- name?: string;
533
- styles?: any;
165
+ query?: string; condition?: string; name?: string; styles?: any;
534
166
  steps?: Record<string, Record<string, string>>;
535
- properties?: Record<string, string>;
536
- descriptors?: Record<string, string>;
167
+ properties?: Record<string, string>; descriptors?: Record<string, string>;
537
168
  }
538
169
 
539
- export interface NestedRule {
540
- selector: string;
541
- styles: Record<string, string | number>;
542
- }
543
-
544
- export interface ThemeBlock {
545
- name: string;
546
- styles: StyleDefinition;
547
- tokens: any;
548
- fallback: any;
549
- }
170
+ export interface NestedRule { selector: string; styles: Record<string, string | number>; }
171
+ export interface ThemeBlock { name: string; styles: StyleDefinition; tokens: any; fallback: any; }
550
172
 
551
173
  export interface StyleDefinition {
552
174
  selectors: string[];
@@ -557,590 +179,193 @@ export interface StyleDefinition {
557
179
  [cssProperty: string]: any;
558
180
  }
559
181
 
182
+ // ============================================================================
183
+ // AT-Rule Processing
184
+ // ============================================================================
185
+
560
186
  function processAtRule(rule: AtRule, parentSelectors: string[] | null = null): string {
561
187
  let output = '';
562
-
563
- switch(rule.type) {
188
+ switch (rule.type) {
564
189
  case 'media':
565
190
  output = `@media ${rule.query} {\n`;
566
191
  if (rule.styles && typeof rule.styles === 'object') {
567
- let ruleBody = '';
568
- for (const prop in rule.styles) {
569
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
570
- ruleBody += ` ${kebabKey}: ${rule.styles[prop]};\n`;
571
- }
572
- if (ruleBody.trim()) {
573
- const selector = (parentSelectors && parentSelectors.length > 0)
574
- ? parentSelectors.join(', ')
575
- : '.unknown-selector';
576
- const sourceLocation = getSourceLocation();
577
- if (enableSourceComments && sourceLocation) {
578
- output += ` /* Generated from: ${sourceLocation} */\n`;
579
- }
580
- output += ` ${selector} {\n${ruleBody} }\n`;
192
+ let body = '';
193
+ for (const prop in rule.styles) body += ` ${prop.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${rule.styles[prop]};\n`;
194
+ if (body.trim()) {
195
+ const sel = parentSelectors?.length ? parentSelectors.join(', ') : '.unknown-selector';
196
+ output += ` ${sel} {\n${body} }\n`;
581
197
  }
582
198
  }
583
199
  output += '}\n';
584
200
  break;
585
-
586
201
  case 'keyframes':
587
202
  output = `@keyframes ${rule.name} {\n`;
588
203
  for (const step in rule.steps) {
589
204
  output += ` ${step} {\n`;
590
205
  for (const prop in rule.steps[step]) {
591
- if (prop !== 'selectors') {
592
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
593
- output += ` ${kebabKey}: ${rule.steps[step][prop]};\n`;
594
- }
206
+ if (prop !== 'selectors') output += ` ${prop.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${rule.steps[step][prop]};\n`;
595
207
  }
596
208
  output += ' }\n';
597
209
  }
598
210
  output += '}\n';
599
211
  break;
600
-
601
212
  case 'font-face':
602
213
  output = '@font-face {\n';
603
214
  for (const prop in rule.properties) {
604
- if (prop !== 'selectors') {
605
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
606
- output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
607
- }
215
+ if (prop !== 'selectors') output += ` ${prop.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${rule.properties[prop]};\n`;
608
216
  }
609
217
  output += '}\n';
610
218
  break;
611
-
612
- default:
613
- // Handle other AT-rules
614
- output = '';
615
- break;
616
219
  }
617
-
618
220
  return output;
619
221
  }
620
222
 
621
223
  // ============================================================================
622
- // Run & Compile Functions
224
+ // Compile & Run
623
225
  // ============================================================================
624
226
 
625
227
  export const run = (...args: any[]): string => {
626
- // Validate inputs
627
228
  if (args.length === 0) return '';
628
-
629
- const validStyles = args.filter(value => value && typeof value === 'object');
229
+ const validStyles = args.filter(v => v && typeof v === 'object');
630
230
  if (validStyles.length === 0) return '';
631
231
 
632
232
  let cssOutput = '';
633
233
  const styleObjs: any[] = [];
634
-
234
+
635
235
  args.forEach((value) => {
636
236
  if (!value) return;
637
237
  styleObjs.push(value);
638
-
639
- // Standalone at-rules (keyframes, etc.)
640
- if (value.type && !value.selectors) {
641
- cssOutput += processAtRule(value) + '\n';
642
- return;
643
- }
644
-
645
- if (value.selectors) {
646
- let mainRuleBody = '';
647
- let subRulesOutput = '';
238
+
239
+ if (value.type && !value.selectors) { cssOutput += processAtRule(value) + '\n'; return; }
240
+ if (!value.selectors) return;
241
+
242
+ let mainBody = '', subOutput = '';
243
+ for (const key in value) {
244
+ if (!value.hasOwnProperty(key)) continue;
245
+ if (['selectors','atRules','hover','nestedRules','use','nest','themes','_componentName','_generateComponent','_framework','_propsDefinition'].includes(key)) continue;
648
246
 
649
- for (const key in value) {
650
- if (!value.hasOwnProperty(key)) continue;
651
-
652
- // Skip metadata and handled special keys
653
- if ([
654
- 'selectors', 'atRules', 'hover', 'nestedRules', 'use', 'nest', 'themes',
655
- '_componentName', '_generateComponent', '_framework', '_propsDefinition'
656
- ].includes(key)) continue;
657
-
658
- // Handle AT-rules
659
- if (key === 'atRules' && Array.isArray(value[key])) {
660
- value[key].forEach((rule: any) => {
661
- subRulesOutput += processAtRule(rule, value.selectors);
662
- });
663
- continue;
664
- }
665
-
666
- // Handle Nested Rules
667
- if (key === 'nestedRules' && Array.isArray(value[key])) {
668
- value[key].forEach((rule: any) => {
669
- let nestedBody = '';
670
- for (const prop in rule.styles) {
671
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
672
- nestedBody += ` ${kebabKey}: ${rule.styles[prop]};\n`;
673
- }
674
- if (nestedBody) {
675
- subRulesOutput += `${value.selectors.join(', ')} ${rule.selector} {\n${nestedBody} }\n`;
676
- }
677
- });
678
- continue;
679
- }
680
-
681
- // Handle Hover State
682
- if (key === 'hover' && typeof value[key] === 'object') {
683
- let hoverBody = '';
684
- for (const hoverKey in value[key]) {
685
- const kebabKey = hoverKey.replace(/([A-Z])/g, '-$1').toLowerCase();
686
- hoverBody += ` ${kebabKey}: ${value[key][hoverKey]};\n`;
687
- }
688
- if (hoverBody) {
689
- subRulesOutput += `${value.selectors.join(', ')}:hover {\n${hoverBody}}\n`;
690
- }
691
- continue;
692
- }
693
-
694
- // Standard CSS Property
695
- const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
696
- mainRuleBody += ` ${kebabKey}: ${value[key]};\n`;
247
+ if (key === 'atRules' && Array.isArray(value[key])) {
248
+ value[key].forEach((r: any) => { subOutput += processAtRule(r, value.selectors); });
249
+ continue;
697
250
  }
698
-
699
- if (mainRuleBody.trim()) {
700
- cssOutput += `${value.selectors.join(', ')} {\n${mainRuleBody}}\n`;
251
+ if (key === 'nestedRules' && Array.isArray(value[key])) {
252
+ value[key].forEach((r: any) => {
253
+ let nb = '';
254
+ for (const p in r.styles) nb += ` ${p.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${r.styles[p]};\n`;
255
+ if (nb) subOutput += `${value.selectors.join(', ')} ${r.selector} {\n${nb} }\n`;
256
+ });
257
+ continue;
701
258
  }
702
- cssOutput += subRulesOutput;
259
+ if (key === 'hover' && typeof value[key] === 'object') {
260
+ let hb = '';
261
+ for (const hk in value[key]) hb += ` ${hk.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${value[key][hk]};\n`;
262
+ if (hb) subOutput += `${value.selectors.join(', ')}:hover {\n${hb}}\n`;
263
+ continue;
264
+ }
265
+ mainBody += ` ${key.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${value[key]};\n`;
703
266
  }
267
+ if (mainBody.trim()) cssOutput += `${value.selectors.join(', ')} {\n${mainBody}}\n`;
268
+ cssOutput += subOutput;
704
269
  });
705
-
706
- // Cleanup whitespace
707
- cssOutput = cssOutput.replace(/\n{3,}/g, '\n\n').trim();
708
-
709
- // Handle Atomic Optimization inside recipes/runs
710
- if (atomicOptimizer && atomicOptimizer.options.enabled) {
711
- const result = atomicOptimizer.optimize(styleObjs);
712
- return result.css;
713
- }
714
270
 
271
+ cssOutput = cssOutput.replace(/\n{3,}/g, '\n\n').trim();
272
+ if (atomicOptimizer?.options.enabled) return atomicOptimizer.optimize(styleObjs).css;
715
273
  return cssOutput;
716
274
  };
717
275
 
718
- function generateCSSFromCollected(collected: StyleDefinition[]): string {
719
- let css = '';
720
- for (const style of collected) {
721
- if (!style.selectors) continue;
722
-
723
- let normalStyles = '';
724
- let hoverStyles = '';
725
-
726
- for (const [key, value] of Object.entries(style)) {
727
- if (key === 'selectors') continue;
728
-
729
- if (key === 'hover' && typeof value === 'object') {
730
- for (const [hoverKey, hoverValue] of Object.entries(value)) {
731
- const kebabKey = hoverKey.replace(/([A-Z])/g, '-$1').toLowerCase();
732
- hoverStyles += ` ${kebabKey}: ${hoverValue};\n`;
733
- }
734
- } else {
735
- const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
736
- normalStyles += ` ${kebabKey}: ${value};\n`;
737
- }
738
- }
739
-
740
- if (normalStyles) {
741
- css += `${style.selectors.join(', ')} {\n${normalStyles}}\n`;
742
- }
743
- if (hoverStyles) {
744
- css += `${style.selectors.join(', ')}:hover {\n${hoverStyles}}\n`;
745
- }
746
- }
747
- return css;
748
- }
749
-
750
276
  export const compile = (obj: Record<string, StyleDefinition>): string => {
751
277
  let cssString = '';
752
278
  const collected: StyleDefinition[] = [];
753
279
  const processedSelectors = new Set<string>();
754
-
280
+
755
281
  for (const key in obj) {
756
282
  if (!obj.hasOwnProperty(key)) continue;
757
283
  const element = obj[key];
758
-
284
+
759
285
  if (element && (element as any).variants && typeof (element as any).compileAll === 'function') {
760
- const cleanKey = key.includes('_') ? key.split('_').pop() : key;
761
- const recipeOutput = (element as any).compileAll(cleanKey);
762
- cssString += recipeOutput + '\n';
286
+ cssString += (element as any).compileAll(key.includes('_') ? key.split('_').pop() : key) + '\n';
763
287
  continue;
764
288
  }
289
+ if (!element?.selectors?.[0]) continue;
765
290
 
766
- // 1. Basic Validation
767
- if (!element || !element.selectors || !element.selectors[0]) continue;
768
-
769
291
  const selectorKey = element.selectors.join(',');
770
292
  if (processedSelectors.has(selectorKey)) continue;
771
-
772
293
  processedSelectors.add(selectorKey);
773
294
  collected.push(element);
774
-
295
+
775
296
  const sourceLocation = getSourceLocation();
776
- let elementCSS = '';
777
- let subRulesCSS = '';
778
-
779
- // 2. Timeline Snapshot (Internal Debugging)
780
- if (timelineEnabled) {
297
+ let elementCSS = '', subRulesCSS = '';
298
+
299
+ // Timeline snapshot
300
+ if (timelineActive()) {
781
301
  const styles: Record<string, any> = {};
782
302
  for (const prop in element) {
783
- if (!['selectors', 'atRules', 'hover', 'nestedRules', 'use', 'nest', 'themes'].includes(prop)) {
784
- styles[prop] = element[prop];
785
- }
303
+ if (!['selectors','atRules','hover','nestedRules','use','nest','themes'].includes(prop)) styles[prop] = element[prop];
786
304
  }
787
- takeSnapshot(element.selectors[0], styles, sourceLocation || 'unknown');
305
+ ts(element.selectors[0], styles, sourceLocation || 'unknown');
788
306
  }
789
-
790
- // 3. Process Standard CSS Properties
307
+
791
308
  for (const prop in element) {
792
309
  if (prop.startsWith('.') || prop.startsWith('&')) continue;
793
- // Skip metadata and special blocks
794
- if (['selectors', 'atRules', 'hover', 'use', 'nest', 'themes', 'nestedRules', '_componentName', '_generateComponent', '_framework'].includes(prop)) continue;
310
+ if (['selectors','atRules','hover','use','nest','themes','nestedRules','_componentName','_generateComponent','_framework'].includes(prop)) continue;
795
311
  if (prop.startsWith('_') || !element.hasOwnProperty(prop)) continue;
796
-
797
312
  const value = element[prop];
798
313
  if (value === undefined || value === null) continue;
799
-
800
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
801
- elementCSS += ` ${kebabKey}: ${value};\n`;
314
+ elementCSS += ` ${prop.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${value};\n`;
802
315
  }
803
-
804
- // 4. Generate Main Selector Block
805
- if (elementCSS.trim()) {
806
- let block = `${element.selectors.join(', ')} {\n${elementCSS}}\n`;
807
- cssString += addSourceComment(block, sourceLocation);
808
- }
809
-
810
- // 5. Process Hover State
316
+
317
+ if (elementCSS.trim()) cssString += addSourceComment(`${element.selectors.join(', ')} {\n${elementCSS}}\n`, sourceLocation);
318
+
811
319
  if (element.hover && typeof element.hover === 'object') {
812
- let hoverBody = '';
813
- for (const hProp in element.hover) {
814
- const hKebab = hProp.replace(/([A-Z])/g, '-$1').toLowerCase();
815
- hoverBody += ` ${hKebab}: ${element.hover[hProp]};\n`;
816
- }
817
- if (hoverBody) {
818
- let block = `${element.selectors.join(', ')}:hover {\n${hoverBody}}\n`;
819
- cssString += addSourceComment(block, sourceLocation);
820
- }
320
+ let hb = '';
321
+ for (const hp in element.hover) hb += ` ${hp.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${element.hover[hp]};\n`;
322
+ if (hb) cssString += addSourceComment(`${element.selectors.join(', ')}:hover {\n${hb}}\n`, sourceLocation);
821
323
  }
822
-
823
- // 5.5 Process Nested Selectors (The missing link!)
324
+
824
325
  for (const prop in element) {
825
- // If the property starts with . or &, it's a nested selector
826
326
  if ((prop.startsWith('.') || prop.startsWith('&')) && typeof element[prop] === 'object') {
827
- const subElement = element[prop];
828
-
829
- // Resolve selector: replace '&' with parent selector or prepend parent
830
- const parentSelector = element.selectors[0];
831
- const subSelector = prop.startsWith('&')
832
- ? prop.replace('&', parentSelector)
833
- : `${parentSelector} ${prop}`;
834
-
835
- // Recursively compile this sub-block
836
- // We wrap it in a mock StyleDefinition object so compile can eat it
837
- cssString += compile({
838
- [subSelector]: {
839
- selectors: [subSelector],
840
- ...subElement
841
- }
842
- }) + '\n';
327
+ const parentSel = element.selectors[0];
328
+ const subSel = prop.startsWith('&') ? prop.replace('&', parentSel) : `${parentSel} ${prop}`;
329
+ cssString += compile({ [subSel]: { selectors: [subSel], ...element[prop] } }) + '\n';
843
330
  }
844
331
  }
845
-
846
- // 6. Process At-Rules (Media Queries, Keyframes)
332
+
847
333
  if (element.atRules && Array.isArray(element.atRules)) {
848
- element.atRules.forEach((rule: AtRule) => {
849
- subRulesCSS += processAtRule(rule, element.selectors);
850
- });
334
+ element.atRules.forEach((rule: AtRule) => { subRulesCSS += processAtRule(rule, element.selectors); });
851
335
  }
852
-
853
- // 7. Process Themes
336
+
854
337
  if (element.themes && Array.isArray(element.themes)) {
855
338
  element.themes.forEach((theme: ThemeBlock) => {
856
339
  if (theme.styles) {
857
- let themeCSS = '';
858
- for (const tProp in theme.styles) {
859
- if (tProp === 'selectors') continue;
860
- const tKebab = tProp.replace(/([A-Z])/g, '-$1').toLowerCase();
861
- themeCSS += ` ${tKebab}: ${theme.styles[tProp]};\n`;
862
- }
863
- if (themeCSS) {
864
- let block = `${theme.styles.selectors?.join(', ') || element.selectors.join(', ')} {\n${themeCSS}}\n`;
865
- subRulesCSS += addSourceComment(block, sourceLocation);
340
+ let tc = '';
341
+ for (const tp in theme.styles) {
342
+ if (tp === 'selectors') continue;
343
+ tc += ` ${tp.replace(/([A-Z])/g, '-$1').toLowerCase()}: ${theme.styles[tp]};\n`;
866
344
  }
345
+ if (tc) subRulesCSS += addSourceComment(`${theme.styles.selectors?.join(', ') || element.selectors.join(', ')} {\n${tc}}\n`, sourceLocation);
867
346
  }
868
347
  });
869
348
  }
870
-
349
+
871
350
  cssString += subRulesCSS;
872
351
  }
873
-
874
- // 8. Handle Atomic Optimization or Final Output
875
- if (atomicOptimizer && atomicOptimizer.options.enabled) {
352
+
353
+ if (atomicOptimizer?.options.enabled) {
876
354
  const result = atomicOptimizer.optimize(collected);
877
355
  chains.cssOutput = result.css;
878
356
  return result.css;
879
357
  }
880
-
358
+
881
359
  chains.cssOutput = cssString.trim();
882
360
  return chains.cssOutput;
883
361
  };
884
362
 
885
363
  // ============================================================================
886
- // Recipe System
364
+ // Initialize
887
365
  // ============================================================================
888
366
 
889
- export interface RecipeOptions<TVariants extends Record<string, Record<string, any>>> {
890
- base?: StyleDefinition | (() => StyleDefinition);
891
- variants?: TVariants;
892
- defaultVariants?: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>;
893
- compoundVariants?: Array<{
894
- variants: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>;
895
- style: StyleDefinition | (() => StyleDefinition);
896
- }>;
897
- }
898
-
899
- export type Recipe<TVariants extends Record<string, Record<string, any>>> = {
900
- (selection?: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>): StyleDefinition;
901
- variants: TVariants;
902
- defaultVariants: Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>;
903
- base: StyleDefinition;
904
- getAllVariants: () => Array<Partial<{ [K in keyof TVariants]: keyof TVariants[K] }>>;
905
- compileAll: () => string;
906
- getVariantClassNames: () => Record<string, string>;
907
- };
908
-
909
- export function recipe<TVariants extends Record<string, Record<string, any>>>(
910
- options: RecipeOptions<TVariants>
911
- ): Recipe<TVariants> {
912
- const {
913
- base,
914
- variants = {} as TVariants,
915
- defaultVariants = {},
916
- compoundVariants = []
917
- } = options;
918
-
919
- const baseStyle = typeof base === 'function' ? (base as () => StyleDefinition)() : base;
920
- const variantStyles: Record<string, Record<string, StyleDefinition>> = {};
921
-
922
- for (const [variantName, variantMap] of Object.entries(variants)) {
923
- variantStyles[variantName] = {};
924
- for (const [variantKey, variantStyle] of Object.entries(variantMap as Record<string, any>)) {
925
- variantStyles[variantName][variantKey] = typeof variantStyle === 'function'
926
- ? (variantStyle as () => StyleDefinition)()
927
- : variantStyle;
928
- }
929
- }
930
-
931
- const compoundStyles = compoundVariants.map(cv => ({
932
- condition: cv.variants || cv,
933
- style: typeof cv.style === 'function' ? (cv.style as () => StyleDefinition)() : cv.style
934
- }));
935
-
936
- function mergeStyles(...styles: (StyleDefinition | undefined)[]): StyleDefinition {
937
- const merged: StyleDefinition = { selectors: [] } as StyleDefinition;
938
- for (const style of styles) {
939
- if (!style) continue;
940
- for (const [key, value] of Object.entries(style)) {
941
- if (key === 'selectors') {
942
- // Prevent duplicate selectors
943
- const newSelectors = Array.isArray(value) ? value : [value];
944
- merged.selectors = [...new Set([...(merged.selectors || []), ...newSelectors])];
945
- } else if (key === 'hover' && typeof value === 'object') {
946
- if (!merged.hover) merged.hover = {};
947
- Object.assign(merged.hover, value);
948
- } else if (key !== 'selectors') {
949
- (merged as any)[key] = value;
950
- }
951
- }
952
- }
953
- return merged;
954
- }
955
-
956
- function pick(variantSelection: Partial<Record<keyof TVariants, any>> = {}): StyleDefinition {
957
- const selected = { ...defaultVariants, ...variantSelection } as Record<string, any>;
958
- const stylesToMerge: StyleDefinition[] = [];
959
-
960
- if (baseStyle) stylesToMerge.push(baseStyle);
961
- for (const [variantName, variantValue] of Object.entries(selected)) {
962
- const variantStyle = variantStyles[variantName]?.[variantValue];
963
- if (variantStyle) stylesToMerge.push(variantStyle);
964
- }
965
- for (const cv of compoundStyles) {
966
- const matches = Object.entries(cv.condition).every(
967
- ([key, value]) => selected[key] === value
968
- );
969
- if (matches && cv.style) stylesToMerge.push(cv.style);
970
- }
971
-
972
- const merged = mergeStyles(...stylesToMerge);
973
- let styleBuilder: any = chain();
974
-
975
- for (const [prop, value] of Object.entries(merged)) {
976
- if (prop === 'selectors' || prop === 'hover') continue;
977
- if (styleBuilder[prop]) {
978
- styleBuilder = styleBuilder[prop](value);
979
- }
980
- }
981
-
982
- if (merged.hover) {
983
- styleBuilder = styleBuilder.hover();
984
- for (const [hoverProp, hoverValue] of Object.entries(merged.hover)) {
985
- if (styleBuilder[hoverProp]) {
986
- styleBuilder = styleBuilder[hoverProp](hoverValue);
987
- }
988
- }
989
- styleBuilder = styleBuilder.end();
990
- }
991
-
992
- const selectors = merged.selectors || [];
993
- return styleBuilder.$el(...selectors);
994
- }
995
-
996
- (pick as any).variants = variants;
997
- (pick as any).defaultVariants = defaultVariants;
998
- (pick as any).base = baseStyle;
999
-
1000
- (pick as any).getAllVariants = (): Array<Partial<Record<keyof TVariants, any>>> => {
1001
- const result: Array<Partial<Record<keyof TVariants, any>>> = [];
1002
- const variantKeys = Object.keys(variants) as (keyof TVariants)[];
1003
-
1004
- function generate(current: Partial<Record<keyof TVariants, any>>, index: number): void {
1005
- if (index === variantKeys.length) {
1006
- result.push({ ...current });
1007
- return;
1008
- }
1009
- const variantName = variantKeys[index];
1010
- for (const variantValue of Object.keys(variants[variantName] as Record<string, any>)) {
1011
- current[variantName] = variantValue as any;
1012
- generate(current, index + 1);
1013
- }
1014
- }
1015
-
1016
- generate({}, 0);
1017
- return result;
1018
- };
1019
-
1020
- // Get class names for all variants (useful for component libraries)
1021
- (pick as any).getVariantClassNames = (): Record<string, string> => {
1022
- const allVariants = (pick as any).getAllVariants();
1023
- const classNames: Record<string, string> = {};
1024
-
1025
- for (const variant of allVariants) {
1026
- const variantKey = Object.entries(variant).map(([k, v]) => `${k}-${v}`).join('_');
1027
- const styleDef = pick(variant);
1028
- // Extract class name from selectors
1029
- if (styleDef.selectors && styleDef.selectors[0]) {
1030
- classNames[variantKey] = styleDef.selectors[0].replace(/^\./, '');
1031
- }
1032
- }
1033
-
1034
- return classNames;
1035
- };
1036
-
1037
- (pick as any).compileAll = (): string => {
1038
- const allVariants = (pick as any).getAllVariants();
1039
- const styles: StyleDefinition[] = [];
1040
-
1041
- // Add base style
1042
- if (baseStyle && baseStyle.selectors) {
1043
- styles.push(baseStyle);
1044
- }
1045
-
1046
- // Add all variant styles
1047
- for (const variant of allVariants) {
1048
- const styleDef = pick(variant);
1049
- if (styleDef && styleDef.selectors) {
1050
- styles.push(styleDef);
1051
- }
1052
- }
1053
-
1054
- // Also add individual variant styles for completeness
1055
- for (const variantName of Object.keys(variants)) {
1056
- for (const variantKey of Object.keys(variants[variantName])) {
1057
- const variantStyle = variantStyles[variantName]?.[variantKey];
1058
- if (variantStyle && variantStyle.selectors) {
1059
- styles.push(variantStyle);
1060
- }
1061
- }
1062
- }
1063
-
1064
- // Run compilation
1065
- return run(...styles);
1066
- };
1067
-
1068
- return pick as Recipe<TVariants>;
1069
- }
1070
-
1071
- /**
1072
- * The "Brain": Extracts ChainCSS calls from raw text
1073
- */
1074
- export const scanContent = (text: string): string[] => {
1075
- // FIXED: Better regex for matching nested parentheses
1076
- const regex = /(?:chain|\$t?)\(((?:[^()]|\([^()]*\))*)\)(?:\s*\.\s*[a-zA-Z0-9]+\s*\([^)]*\))*/g;
1077
- const matches = text.match(regex) || [];
1078
- return matches.map(m => m.replace(/\s+/g, ''));
1079
- };
1080
-
1081
- /**
1082
- * The "Worker": Reads the file, uses the Brain, feeds the Optimizer
1083
- */
1084
- export function scanFileForStyles(
1085
- filePath: string,
1086
- optimizer: any,
1087
- source: string | null = null
1088
- ): { foundCount: number; errors: Error[] } {
1089
- const errors: Error[] = [];
1090
- let foundCount = 0;
1091
-
1092
- try {
1093
- const content = source !== null ? source : fs.readFileSync(filePath, 'utf8');
1094
- if (!content || content.trim().length === 0) {
1095
- return { foundCount: 0, errors };
1096
- }
1097
-
1098
- // FIXED: Better regex that matches nested parentheses
1099
- const styleRegex = /(?:chain|\$)\(((?:[^()]|\([^()]*\))*)\)/g;
1100
-
1101
- let match;
1102
-
1103
- while ((match = styleRegex.exec(content)) !== null) {
1104
- try {
1105
- const styleBody = match[1].trim();
1106
-
1107
- // Clean up quotes if it's a string inside the parens
1108
- const cleanBody = styleBody.replace(/^['"`]|['"`]$/g, '');
1109
-
1110
- if (cleanBody) {
1111
- // Feed into the optimizer
1112
- if (optimizer && typeof optimizer.trackStyles === 'function') {
1113
- optimizer.trackStyles([{ selectors: { '&': cleanBody } }]);
1114
- }
1115
- foundCount++;
1116
- }
1117
- } catch (parseError) {
1118
- errors.push(parseError as Error);
1119
- if (process.env.DEBUG) {
1120
- console.error(`[Scanner] Parse error in ${filePath}:`, parseError);
1121
- }
1122
- }
1123
- }
1124
-
1125
- if (foundCount > 0 && process.env.DEBUG) {
1126
- console.log(chalk.magenta(`[Scanner] Found ${foundCount} styles in ${filePath}`));
1127
- }
1128
- } catch (err) {
1129
- errors.push(err as Error);
1130
- if (process.env.DEBUG) {
1131
- console.error(`[Scanner] Error processing ${filePath}:`, err);
1132
- }
1133
- }
1134
-
1135
- return { foundCount, errors };
1136
- }
1137
-
1138
- // Initialize properties (non-blocking)
1139
367
  chains.initializeProperties().catch((err: Error) => {
1140
- if (process.env.DEBUG) {
1141
- console.error('Failed to load CSS properties:', err.message);
1142
- }
368
+ if (process.env.DEBUG) console.error('Failed to load CSS properties:', err.message);
1143
369
  });
1144
370
 
1145
- // Exports
1146
- export { atomicOptimizer, chains as chainObject };
371
+ export { atomicOptimizer, chains as chainObject };