chaincss 2.0.7 → 2.1.1

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.
Files changed (159) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/CODE_OF_CONDUCT.md +21 -0
  3. package/CONTRIBUTING.md +28 -0
  4. package/README.md +455 -226
  5. package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +1 -0
  6. package/demo/index.html +16 -0
  7. package/demo/package.json +20 -0
  8. package/demo/src/App.tsx +117 -0
  9. package/demo/src/chaincss-barrel.ts +9 -0
  10. package/demo/src/main.tsx +8 -0
  11. package/demo/src/styles.chain.ts +300 -0
  12. package/demo/vite.config.ts +46 -0
  13. package/dist/cli/commands/build.d.ts +0 -1
  14. package/dist/cli/commands/cache.d.ts +1 -0
  15. package/dist/cli/commands/init.d.ts +6 -3
  16. package/dist/cli/commands/timeline.d.ts +0 -1
  17. package/dist/cli/commands/watch.d.ts +0 -1
  18. package/dist/cli/index.d.ts +0 -1
  19. package/dist/cli/index.js +3213 -5296
  20. package/dist/cli/types.d.ts +51 -20
  21. package/dist/cli/utils/config-loader.d.ts +0 -1
  22. package/dist/cli/utils/file-utils.d.ts +27 -3
  23. package/dist/cli/utils/logger.d.ts +0 -1
  24. package/dist/compiler/Chain.d.ts +215 -0
  25. package/dist/compiler/animations.d.ts +76 -0
  26. package/dist/compiler/atomic-optimizer.d.ts +47 -12
  27. package/dist/compiler/breakpoints.d.ts +46 -0
  28. package/dist/compiler/btt.d.ts +36 -60
  29. package/dist/compiler/cache-manager.d.ts +58 -4
  30. package/dist/compiler/commonProps.d.ts +0 -1
  31. package/dist/compiler/content-addressable-cache.d.ts +78 -0
  32. package/dist/compiler/helpers.d.ts +54 -0
  33. package/dist/compiler/index.d.ts +16 -9
  34. package/dist/compiler/index.js +4450 -4316
  35. package/dist/compiler/prefixer.d.ts +17 -1
  36. package/dist/compiler/shorthands.d.ts +28 -0
  37. package/dist/compiler/suggestions.d.ts +43 -0
  38. package/dist/compiler/theme-contract.d.ts +16 -27
  39. package/dist/compiler/token-resolver.d.ts +69 -0
  40. package/dist/compiler/tokens.d.ts +33 -8
  41. package/dist/core/auto-detector.d.ts +34 -0
  42. package/dist/core/common-utils.d.ts +97 -0
  43. package/dist/core/compiler.d.ts +63 -23
  44. package/dist/core/constants.d.ts +137 -36
  45. package/dist/core/smart-chain.d.ts +3 -0
  46. package/dist/core/types.d.ts +122 -15
  47. package/dist/core/utils.d.ts +134 -17
  48. package/dist/index.d.ts +52 -8
  49. package/dist/index.js +7090 -5578
  50. package/dist/plugins/vite.d.ts +7 -5
  51. package/dist/plugins/vite.js +2964 -25641
  52. package/dist/plugins/webpack.d.ts +24 -1
  53. package/dist/plugins/webpack.js +209 -72
  54. package/dist/runtime/Chain.d.ts +32 -0
  55. package/dist/runtime/auto-hooks.d.ts +11 -0
  56. package/dist/runtime/hmr.d.ts +22 -2
  57. package/dist/runtime/index.d.ts +3 -2
  58. package/dist/runtime/index.js +3648 -301
  59. package/dist/runtime/injector.d.ts +39 -72
  60. package/dist/runtime/react.d.ts +17 -12
  61. package/dist/runtime/svelte.d.ts +15 -0
  62. package/dist/runtime/types.d.ts +126 -4
  63. package/dist/runtime/utils.d.ts +0 -1
  64. package/dist/runtime/vue.d.ts +34 -14
  65. package/package.json +59 -66
  66. package/src/cli/commands/build.ts +133 -0
  67. package/src/cli/commands/cache.ts +371 -0
  68. package/src/cli/commands/init.ts +230 -0
  69. package/src/cli/commands/timeline.ts +435 -0
  70. package/src/cli/commands/watch.ts +211 -0
  71. package/src/cli/index.ts +226 -0
  72. package/src/cli/types.ts +100 -0
  73. package/src/cli/utils/config-loader.ts +174 -0
  74. package/src/cli/utils/file-utils.ts +139 -0
  75. package/src/cli/utils/logger.ts +74 -0
  76. package/src/compiler/Chain.ts +831 -0
  77. package/src/compiler/animations.ts +517 -0
  78. package/src/compiler/atomic-optimizer.ts +786 -0
  79. package/src/compiler/breakpoints.ts +347 -0
  80. package/src/compiler/btt.ts +1147 -0
  81. package/src/compiler/cache-manager.ts +446 -0
  82. package/src/compiler/commonProps.ts +18 -0
  83. package/src/compiler/content-addressable-cache.ts +478 -0
  84. package/src/compiler/helpers.ts +407 -0
  85. package/src/compiler/index.ts +72 -0
  86. package/src/compiler/prefixer.ts +720 -0
  87. package/src/compiler/shorthands.ts +558 -0
  88. package/src/compiler/suggestions.ts +436 -0
  89. package/src/compiler/theme-contract.ts +197 -0
  90. package/src/compiler/token-resolver.ts +241 -0
  91. package/src/compiler/tokens.ts +612 -0
  92. package/src/core/auto-detector.ts +187 -0
  93. package/src/core/common-utils.ts +423 -0
  94. package/src/core/compiler.ts +835 -0
  95. package/src/core/constants.ts +424 -0
  96. package/src/core/index.ts +107 -0
  97. package/src/core/smart-chain.ts +163 -0
  98. package/src/core/types.ts +257 -0
  99. package/src/core/utils.ts +598 -0
  100. package/src/index.ts +208 -0
  101. package/src/plugins/vite.d.ts +316 -0
  102. package/src/plugins/vite.ts +424 -0
  103. package/src/plugins/webpack.d.ts +289 -0
  104. package/src/plugins/webpack.ts +416 -0
  105. package/src/runtime/Chain.ts +242 -0
  106. package/src/runtime/auto-hooks.tsx +127 -0
  107. package/src/runtime/auto-vue.ts +72 -0
  108. package/src/runtime/hmr.ts +212 -0
  109. package/src/runtime/index.ts +82 -0
  110. package/src/runtime/injector.ts +273 -0
  111. package/src/runtime/react.tsx +269 -0
  112. package/src/runtime/svelte.ts +15 -0
  113. package/src/runtime/types.ts +256 -0
  114. package/src/runtime/utils.ts +128 -0
  115. package/src/runtime/vite-env.d.ts +120 -0
  116. package/src/runtime/vue.ts +231 -0
  117. package/tsconfig.build.json +41 -0
  118. package/tsconfig.json +25 -0
  119. package/tsconfig.runtimes.json +18 -0
  120. package/dist/cli/cli.cjs +0 -7
  121. package/dist/cli/commands/build.d.ts.map +0 -1
  122. package/dist/cli/commands/compile.d.ts +0 -3
  123. package/dist/cli/commands/compile.d.ts.map +0 -1
  124. package/dist/cli/commands/init.d.ts.map +0 -1
  125. package/dist/cli/commands/timeline.d.ts.map +0 -1
  126. package/dist/cli/commands/watch.d.ts.map +0 -1
  127. package/dist/cli/index.d.ts.map +0 -1
  128. package/dist/cli/types.d.ts.map +0 -1
  129. package/dist/cli/utils/config-loader.d.ts.map +0 -1
  130. package/dist/cli/utils/file-utils.d.ts.map +0 -1
  131. package/dist/cli/utils/logger.d.ts.map +0 -1
  132. package/dist/compiler/atomic-optimizer.d.ts.map +0 -1
  133. package/dist/compiler/btt.d.ts.map +0 -1
  134. package/dist/compiler/cache-manager.d.ts.map +0 -1
  135. package/dist/compiler/commonProps.d.ts.map +0 -1
  136. package/dist/compiler/index.d.ts.map +0 -1
  137. package/dist/compiler/prefixer.d.ts.map +0 -1
  138. package/dist/compiler/theme-contract.d.ts.map +0 -1
  139. package/dist/compiler/tokens.d.ts.map +0 -1
  140. package/dist/compiler/types.d.ts +0 -57
  141. package/dist/compiler/types.d.ts.map +0 -1
  142. package/dist/core/compiler.d.ts.map +0 -1
  143. package/dist/core/constants.d.ts.map +0 -1
  144. package/dist/core/index.d.ts +0 -4
  145. package/dist/core/index.d.ts.map +0 -1
  146. package/dist/core/types.d.ts.map +0 -1
  147. package/dist/core/utils.d.ts.map +0 -1
  148. package/dist/index.d.ts.map +0 -1
  149. package/dist/plugins/vite.d.ts.map +0 -1
  150. package/dist/plugins/webpack.d.ts.map +0 -1
  151. package/dist/runtime/hmr.d.ts.map +0 -1
  152. package/dist/runtime/index.d.ts.map +0 -1
  153. package/dist/runtime/injector.d.ts.map +0 -1
  154. package/dist/runtime/react.d.ts.map +0 -1
  155. package/dist/runtime/react.js +0 -324
  156. package/dist/runtime/types.d.ts.map +0 -1
  157. package/dist/runtime/utils.d.ts.map +0 -1
  158. package/dist/runtime/vue.d.ts.map +0 -1
  159. package/dist/runtime/vue.js +0 -286
@@ -0,0 +1,416 @@
1
+ // chaincss/src/plugins/webpack.ts
2
+
3
+ import path from 'path';
4
+ import { execSync, spawn } from 'child_process';
5
+ import fs from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import crypto from 'crypto';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ export interface ChainCSSLoaderOptions {
13
+ /**
14
+ * Mode: 'build' for production (zero-runtime), 'runtime' for development
15
+ * @default process.env.NODE_ENV === 'production' ? 'build' : 'runtime'
16
+ */
17
+ mode?: 'build' | 'runtime';
18
+
19
+ /**
20
+ * Enable atomic CSS optimization
21
+ * @default false
22
+ */
23
+ atomic?: boolean;
24
+
25
+ /**
26
+ * Enable CSS minification
27
+ * @default process.env.NODE_ENV === 'production'
28
+ */
29
+ minify?: boolean;
30
+
31
+ /**
32
+ * Enable source maps
33
+ * @default false
34
+ */
35
+ sourceMap?: boolean;
36
+
37
+ /**
38
+ * Output directory for compiled CSS
39
+ * @default '.chaincss-cache'
40
+ */
41
+ outputDir?: string;
42
+
43
+ /**
44
+ * Verbose logging
45
+ * @default false
46
+ */
47
+ verbose?: boolean;
48
+
49
+ /**
50
+ * Enable CSS extraction to separate file
51
+ * @default false
52
+ */
53
+ extractCSS?: boolean;
54
+
55
+ /**
56
+ * Enable Hot Module Replacement
57
+ * @default true in development
58
+ */
59
+ hmr?: boolean;
60
+
61
+ /**
62
+ * Custom cache key for compilation
63
+ */
64
+ cacheKey?: string;
65
+
66
+ /**
67
+ * Framework to generate components for
68
+ * @default 'auto'
69
+ */
70
+ framework?: 'react' | 'vue' | 'svelte' | 'solid' | 'auto';
71
+ }
72
+
73
+ interface LoaderContext {
74
+ async: () => (err: Error | null, code?: string) => void;
75
+ getOptions: () => ChainCSSLoaderOptions;
76
+ resourcePath: string;
77
+ context: string;
78
+ cacheable: (flag: boolean) => void;
79
+ addDependency: (file: string) => void;
80
+ emitFile: (name: string, content: string, sourceMap?: any) => void;
81
+ emitWarning: (warning: Error) => void;
82
+ emitError: (error: Error) => void;
83
+ }
84
+
85
+ interface StyleExport {
86
+ name: string;
87
+ className: string;
88
+ }
89
+
90
+ /**
91
+ * Generate a unique hash for cache busting
92
+ */
93
+ function generateHash(content: string): string {
94
+ return crypto.createHash('md5').update(content).digest('hex').slice(0, 8);
95
+ }
96
+
97
+ /**
98
+ * Extract style variable names and their values from source code
99
+ */
100
+ function extractStyleExports(source: string): StyleExport[] {
101
+ const exports: StyleExport[] = [];
102
+
103
+ // Match const declarations that use chain() or $()
104
+ const constRegex = /const\s+(\w+)\s*=\s*(?:chain|\$)\(\s*\)\s*(?:\.\w+\([^)]*\)\s*)*\.\$el\(['"`]([^'"`]+)['"`]\)/g;
105
+ let match;
106
+
107
+ while ((match = constRegex.exec(source)) !== null) {
108
+ exports.push({
109
+ name: match[1],
110
+ className: match[2]
111
+ });
112
+ }
113
+
114
+ // Also match export declarations
115
+ const exportRegex = /export\s+const\s+(\w+)\s*=\s*(?:chain|\$)\(\s*\)\s*(?:\.\w+\([^)]*\)\s*)*\.\$el\(['"`]([^'"`]+)['"`]\)/g;
116
+ while ((match = exportRegex.exec(source)) !== null) {
117
+ exports.push({
118
+ name: match[1],
119
+ className: match[2]
120
+ });
121
+ }
122
+
123
+ return exports;
124
+ }
125
+
126
+ /**
127
+ * Generate runtime code for development mode
128
+ */
129
+ function generateRuntimeCode(source: string, styleExports: StyleExport[], resourcePath: string, options: ChainCSSLoaderOptions): string {
130
+ const styleNames = styleExports.map(e => e.name).join(', ');
131
+ const hmrEnabled = options.hmr !== false && process.env.NODE_ENV !== 'production';
132
+
133
+ // Generate class mapping
134
+ const classMapping = styleExports.map(exp => ` ${exp.name}: '${exp.className}'`).join(',\n');
135
+
136
+ return `
137
+ // Generated by ChainCSS Webpack Loader (Runtime Mode)
138
+ // Source: ${path.basename(resourcePath)}
139
+
140
+ import { chain, enableDebug } from 'chaincss/runtime';
141
+
142
+ ${hmrEnabled ? `// Enable debug in development
143
+ if (process.env.NODE_ENV !== 'production') {
144
+ enableDebug(${options.verbose || false});
145
+ }` : ''}
146
+
147
+ // Original source for reference
148
+ ${source}
149
+
150
+ // Compiled style exports
151
+ export const styles = {
152
+ ${classMapping}
153
+ };
154
+
155
+ export default styles;
156
+
157
+ ${hmrEnabled ? `
158
+ // HMR Support
159
+ if (module.hot) {
160
+ module.hot.accept();
161
+ module.hot.dispose(() => {
162
+ // Cleanup on reload
163
+ const styleId = 'chaincss-${generateHash(resourcePath)}';
164
+ const style = document.getElementById(styleId);
165
+ if (style) style.remove();
166
+ });
167
+ }
168
+ ` : ''}
169
+ `;
170
+ }
171
+
172
+ /**
173
+ * Generate production code with CSS extraction
174
+ */
175
+ function generateProductionCode(
176
+ css: string,
177
+ styleExports: StyleExport[],
178
+ resourcePath: string,
179
+ options: ChainCSSLoaderOptions
180
+ ): string {
181
+ const styleNames = styleExports.map(e => e.name).join(', ');
182
+ const classMapping = styleExports.map(exp => ` ${exp.name}: '${exp.className}'`).join(',\n');
183
+
184
+ // Escape CSS for injection
185
+ const escapedCSS = css
186
+ .replace(/\\/g, '\\\\')
187
+ .replace(/`/g, '\\`')
188
+ .replace(/\$/g, '\\$');
189
+
190
+ if (options.extractCSS) {
191
+ // CSS will be extracted to separate file
192
+ return `
193
+ // Generated by ChainCSS Webpack Loader (Build Mode - Extracted)
194
+ // Source: ${path.basename(resourcePath)}
195
+
196
+ export const styles = {
197
+ ${classMapping}
198
+ };
199
+
200
+ export default styles;
201
+ `;
202
+ } else {
203
+ // CSS will be injected via JavaScript
204
+ return `
205
+ // Generated by ChainCSS Webpack Loader (Build Mode - Injected)
206
+ // Source: ${path.basename(resourcePath)}
207
+
208
+ export const styles = {
209
+ ${classMapping}
210
+ };
211
+
212
+ // Inject CSS
213
+ const css = \`${escapedCSS}\`;
214
+ if (typeof document !== 'undefined' && css && css.trim()) {
215
+ const styleId = 'chaincss-${generateHash(resourcePath)}';
216
+ let style = document.getElementById(styleId);
217
+ if (!style) {
218
+ style = document.createElement('style');
219
+ style.id = styleId;
220
+ style.setAttribute('data-chaincss', 'true');
221
+ style.setAttribute('data-source', ${JSON.stringify(path.basename(resourcePath))});
222
+ document.head.appendChild(style);
223
+ }
224
+ style.textContent = css;
225
+ }
226
+
227
+ export default styles;
228
+ `;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Compile ChainCSS file using the compiler
234
+ */
235
+ async function compileChainCSS(
236
+ source: string,
237
+ filePath: string,
238
+ options: ChainCSSLoaderOptions,
239
+ context: LoaderContext
240
+ ): Promise<{ css: string; classMap: Record<string, string> }> {
241
+ const tempFile = path.join(context.context, `.temp.${generateHash(source)}.chain.js`);
242
+ const outputDir = options.outputDir || path.join(process.cwd(), '.chaincss-cache');
243
+ const baseName = path.basename(filePath, path.extname(filePath));
244
+
245
+ try {
246
+ // Ensure temp directory exists
247
+ const tempDir = path.dirname(tempFile);
248
+ if (!fs.existsSync(tempDir)) {
249
+ fs.mkdirSync(tempDir, { recursive: true });
250
+ }
251
+
252
+ // Write temp file
253
+ fs.writeFileSync(tempFile, source, 'utf8');
254
+ context.addDependency(tempFile);
255
+
256
+ // Ensure output directory exists
257
+ if (!fs.existsSync(outputDir)) {
258
+ fs.mkdirSync(outputDir, { recursive: true });
259
+ }
260
+
261
+ // Build CLI command using the compiler
262
+ const chaincssCli = path.join(__dirname, '../cli/index.js');
263
+
264
+ // Check if CLI exists
265
+ if (!fs.existsSync(chaincssCli)) {
266
+ throw new Error(`ChainCSS CLI not found at ${chaincssCli}. Please ensure chaincss is built.`);
267
+ }
268
+
269
+ const args = [
270
+ chaincssCli,
271
+ 'compile',
272
+ tempFile,
273
+ outputDir,
274
+ '--framework', options.framework || 'auto'
275
+ ];
276
+
277
+ if (options.atomic) args.push('--atomic');
278
+ if (options.minify) args.push('--minify');
279
+ if (options.sourceMap) args.push('--source-map');
280
+ if (options.verbose) args.push('--verbose');
281
+
282
+ if (options.verbose) {
283
+ console.log(`[chaincss-loader] Executing: node ${args.join(' ')}`);
284
+ }
285
+
286
+ // Execute compilation with timeout
287
+ const result = await new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
288
+ const proc = spawn('node', args, {
289
+ cwd: process.cwd(),
290
+ timeout: 30000
291
+ });
292
+
293
+ let stdout = '';
294
+ let stderr = '';
295
+
296
+ proc.stdout.on('data', (data) => { stdout += data.toString(); });
297
+ proc.stderr.on('data', (data) => { stderr += data.toString(); });
298
+
299
+ proc.on('close', (code) => {
300
+ if (code === 0) {
301
+ resolve({ stdout, stderr });
302
+ } else {
303
+ reject(new Error(`Compilation failed with code ${code}: ${stderr}`));
304
+ }
305
+ });
306
+
307
+ proc.on('error', reject);
308
+ });
309
+
310
+ if (options.verbose && result.stdout) {
311
+ console.log(result.stdout);
312
+ }
313
+
314
+ if (result.stderr) {
315
+ context.emitWarning(new Error(result.stderr));
316
+ }
317
+
318
+ // Read generated CSS
319
+ const cssPath = path.join(outputDir, `${baseName}.css`);
320
+ let css = '';
321
+ if (fs.existsSync(cssPath)) {
322
+ css = fs.readFileSync(cssPath, 'utf8');
323
+ if (options.verbose) {
324
+ console.log(`[chaincss-loader] Read CSS: ${css.length} bytes from ${cssPath}`);
325
+ }
326
+ }
327
+
328
+ // Read generated class map
329
+ const classMapPath = path.join(outputDir, `${baseName}.class.js`);
330
+ let classMap: Record<string, string> = {};
331
+ if (fs.existsSync(classMapPath)) {
332
+ const classMapContent = fs.readFileSync(classMapPath, 'utf8');
333
+ // Parse the exported class map
334
+ const exportMatch = classMapContent.match(/export const (\w+) = '([^']+)'/g);
335
+ if (exportMatch) {
336
+ exportMatch.forEach(line => {
337
+ const match = line.match(/export const (\w+) = '([^']+)'/);
338
+ if (match) {
339
+ classMap[match[1]] = match[2];
340
+ }
341
+ });
342
+ }
343
+ }
344
+
345
+ return { css, classMap };
346
+
347
+ } finally {
348
+ // Clean up temp file
349
+ try {
350
+ if (fs.existsSync(tempFile)) {
351
+ fs.unlinkSync(tempFile);
352
+ }
353
+ } catch (e) {
354
+ // Ignore cleanup errors
355
+ }
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Webpack loader for ChainCSS
361
+ * Converts .chain.js / .chain.ts files to static CSS at build time
362
+ */
363
+ export default function chaincssLoader(this: LoaderContext, source: string): void {
364
+ const callback = this.async();
365
+ const options = this.getOptions() || {};
366
+
367
+ // Make loader cacheable
368
+ this.cacheable(true);
369
+
370
+ // Determine mode: default to 'build' in production, 'runtime' in development
371
+ const mode = options.mode || (process.env.NODE_ENV === 'production' ? 'build' : 'runtime');
372
+
373
+ // Extract style exports from source
374
+ const styleExports = extractStyleExports(source);
375
+
376
+ if (options.verbose) {
377
+ console.log(`[chaincss-loader] Processing ${path.basename(this.resourcePath)} in ${mode} mode`);
378
+ console.log(`[chaincss-loader] Found exports: ${styleExports.map(e => e.name).join(', ')}`);
379
+ }
380
+
381
+ // DEVELOPMENT MODE: Use runtime (fast iteration, but has runtime cost)
382
+ if (mode === 'runtime') {
383
+ const code = generateRuntimeCode(source, styleExports, this.resourcePath, options);
384
+ callback(null, code);
385
+ return;
386
+ }
387
+
388
+ // PRODUCTION MODE: Build-time compilation (zero-runtime)
389
+ compileChainCSS(source, this.resourcePath, options, this)
390
+ .then(({ css, classMap }) => {
391
+ // Emit CSS as separate asset if extractCSS is enabled
392
+ if (options.extractCSS && css) {
393
+ const cssFileName = `${path.basename(this.resourcePath, path.extname(this.resourcePath))}.css`;
394
+ this.emitFile(cssFileName, css);
395
+ if (options.verbose) {
396
+ console.log(`[chaincss-loader] Emitted CSS: ${cssFileName} (${css.length} bytes)`);
397
+ }
398
+ }
399
+
400
+ // Generate final code
401
+ const code = generateProductionCode(css, styleExports, this.resourcePath, options);
402
+
403
+ if (options.verbose) {
404
+ console.log(`[chaincss-loader] ✓ Compiled ${path.basename(this.resourcePath)}`);
405
+ console.log(`[chaincss-loader] CSS size: ${css.length} bytes`);
406
+ console.log(`[chaincss-loader] Exports: ${styleExports.length}`);
407
+ }
408
+
409
+ callback(null, code);
410
+ })
411
+ .catch((err: Error) => {
412
+ console.error(`[chaincss-loader] Error compiling ${this.resourcePath}:`, err.message);
413
+ this.emitError(err);
414
+ callback(err);
415
+ });
416
+ }
@@ -0,0 +1,242 @@
1
+ // chaincss/src/runtime/Chain.ts
2
+
3
+ import { macros as coreMacros, shorthandMap } from '../compiler/shorthands.js';
4
+
5
+ let debugMode = false;
6
+
7
+ export function enableDebug(enabled: boolean = true): void {
8
+ debugMode = enabled;
9
+ }
10
+
11
+
12
+ /**
13
+ * The mutable registry for runtime plugins.
14
+ */
15
+ const runtimeMacros: Record<string, any> = { ...coreMacros };
16
+
17
+ /**
18
+ * HYDRATION BRIDGE
19
+ * This stores the mapping between "prop:value" and "atomic-class-name".
20
+ * It is populated at app-start by the user.
21
+ */
22
+ let globalManifest: Record<string, string> = {};
23
+
24
+ // Update setManifest to use debug flag
25
+ export const setManifest = (manifest: any) => {
26
+ if (manifest.atomicMap) {
27
+ globalManifest = manifest.atomicMap;
28
+ } else if (manifest.atomicClasses) {
29
+ globalManifest = manifest.atomicClasses;
30
+ } else {
31
+ globalManifest = manifest || {};
32
+ }
33
+
34
+ if (debugMode) {
35
+ console.log('[ChainCSS] Manifest loaded with', Object.keys(globalManifest).length, 'entries');
36
+ }
37
+ };
38
+
39
+ /**
40
+ * THE TOKEN STORE
41
+ * Stores user-defined design tokens (colors, spacing, etc.)
42
+ */
43
+ let globalTokens: Record<string, any> = {};
44
+
45
+ // Update setTokens to use debug flag
46
+ export const setTokens = (tokens: any) => {
47
+ globalTokens = tokens;
48
+ if (debugMode) {
49
+ console.log('[ChainCSS] Tokens updated:', Object.keys(globalTokens));
50
+ }
51
+ };
52
+
53
+ export class RuntimeChain {
54
+ // catcher now tracks both raw styles and pre-baked class names
55
+ private catcher: Record<string, any> = { _classes: [] };
56
+ private componentName: string = '';
57
+ public proxy: any;
58
+
59
+ constructor(private useTokens: boolean = false) {
60
+ // Only these methods are public API
61
+ const PUBLIC_METHODS = new Set([
62
+ 'use', 'hover', '$el', '$name', 'end', 'getCatcher'
63
+ ]);
64
+
65
+ this.proxy = new Proxy(this, {
66
+ /**
67
+ * 1. TRAPS FOR EXTERNAL TOOLS (React, DevTools, JSON.stringify)
68
+ * This prevents the "cyclic object value" error.
69
+ */
70
+ get: (target, prop: string | symbol) => {
71
+ // Handle standard JS/React internal checks
72
+ if (prop === 'toJSON') return () => target.catcher;
73
+ if (prop === 'constructor') return RuntimeChain;
74
+ if (prop === Symbol.toStringTag) return 'RuntimeChain';
75
+ if (prop === '_isChain') return true; // Secret handshake for the hook
76
+
77
+ // If it's a symbol we don't handle, return it from the target
78
+ if (typeof prop !== 'string') return (target as any)[prop];
79
+
80
+ // 2. PUBLIC METHODS only ($el, use, hover, $name, end, getCatcher)
81
+ if (prop in target && PUBLIC_METHODS.has(prop)) {
82
+ const val = (target as any)[prop];
83
+ return typeof val === 'function' ? val.bind(target) : val;
84
+ }
85
+
86
+ // Block internal methods from being called directly
87
+ if (prop in target && typeof (target as any)[prop] === 'function') {
88
+ if (debugMode) {
89
+ console.warn(`[ChainCSS] '${prop}' is an internal method, not part of the public API`);
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ // 3. SHORTHAND RESOLUTION (e.g., 'bg' -> 'backgroundColor')
95
+ const realProp = (shorthandMap as any)[prop] || prop;
96
+
97
+ // 4. MACRO CHECK (.center, .glass, etc.)
98
+ if (runtimeMacros[prop]) {
99
+ return (val: any) => {
100
+ runtimeMacros[prop](val, target.catcher, target.useTokens);
101
+ return target.proxy;
102
+ };
103
+ }
104
+
105
+ // 5. THE "BREAD AND BUTTER" CSS HANDLER
106
+ return (val: any) => {
107
+ // --- TOKEN RESOLUTION ---
108
+ let finalVal = val;
109
+ let valueWithUnit = val;
110
+ const unitless = ['opacity', 'zIndex', 'flex', 'fontWeight', 'flexGrow', 'flexShrink', 'flexBasis', 'order', 'lineHeight', 'animationIterationCount', 'orphans', 'widows', 'columnCount'];
111
+ if (typeof finalVal === 'number' && !unitless.includes(realProp)) {
112
+ valueWithUnit = `${val}px`;
113
+ finalVal = valueWithUnit; // Ensure the dynamic style also has px
114
+ }
115
+
116
+ // --- HYBRID LOOKUP (Static vs Dynamic) ---
117
+ // Match the compiler's format "property:value"
118
+ const lookupKey = `${realProp}:${valueWithUnit}`;
119
+ const staticClass = globalManifest[lookupKey];
120
+
121
+ // In the RuntimeChain class, when adding to _classes, add debug
122
+ if (staticClass) {
123
+ if (!target.catcher._classes.includes(staticClass)) {
124
+ target.catcher._classes.push(staticClass);
125
+ if (debugMode) {
126
+ console.log(`[ChainCSS] Using atomic class: ${staticClass} for ${lookupKey}`);
127
+ }
128
+ }
129
+ } else {
130
+ if (debugMode) {
131
+ console.log(`[ChainCSS] No atomic class for ${lookupKey}, will inject at runtime`);
132
+ }
133
+ target.catcher[realProp] = finalVal;
134
+ }
135
+
136
+ // Return the proxy so we can keep chaining: .bg().color().br()
137
+ return target.proxy;
138
+ };
139
+ }
140
+ });
141
+ }
142
+
143
+
144
+
145
+ use(plugin: any): any {
146
+ const { selectors, atRules, ...styles } = plugin;
147
+
148
+ // Resolve shorthands for incoming plugin styles
149
+ Object.entries(styles).forEach(([key, val]) => {
150
+ const realProp = (shorthandMap as any)[key] || key;
151
+ this.catcher[realProp] = val;
152
+ });
153
+
154
+ return this.proxy;
155
+ }
156
+
157
+ hover(): any {
158
+ const hoverCatcher: Record<string, any> = { _classes: [] };
159
+ const hoverHandler: ProxyHandler<object> = {
160
+ get: (_, prop: string) => {
161
+ if (prop === 'end') {
162
+ return () => {
163
+ this.catcher.hover = { ...this.catcher.hover, ...hoverCatcher };
164
+ return this.proxy;
165
+ };
166
+ }
167
+
168
+ const realProp = (shorthandMap as any)[prop] || prop;
169
+
170
+ return (val: any) => {
171
+ // ✅ FIXED: Use hover: prefix for hover state keys
172
+ const lookupKey = `hover:${realProp}:${val}`;
173
+ const staticClass = globalManifest[lookupKey];
174
+
175
+ if (staticClass) {
176
+ if (!hoverCatcher._classes.includes(staticClass)) {
177
+ hoverCatcher._classes.push(staticClass);
178
+ }
179
+ } else if (runtimeMacros[prop]) {
180
+ runtimeMacros[prop](val, hoverCatcher, this.useTokens);
181
+ } else {
182
+ hoverCatcher[realProp] = val;
183
+ }
184
+ return hoverProxy;
185
+ };
186
+ }
187
+ };
188
+ const hoverProxy = new Proxy({}, hoverHandler);
189
+ return hoverProxy;
190
+ }
191
+
192
+ /**
193
+ * Set the component name for class generation
194
+ */
195
+ $name(name: string): this {
196
+ this.componentName = name;
197
+ return this;
198
+ }
199
+
200
+ /**
201
+ * Finalizes the chain. Returns the style object and resets the catcher.
202
+ */
203
+ $el(name?: string): Record<string, any> {
204
+ // Deep clone to prevent reference sharing
205
+ const result = structuredClone(this.catcher);
206
+
207
+ // Set component name
208
+ result._name = name || this.componentName || 'element';
209
+
210
+ // Strip internal metadata
211
+ delete result._componentName;
212
+ delete result._generateComponent;
213
+ delete result._framework;
214
+ delete result._propsDefinition;
215
+
216
+ // Full reset
217
+ this.catcher = { _classes: [] };
218
+ this.componentName = '';
219
+
220
+ return result;
221
+ }
222
+
223
+ end(name?: string) {
224
+ return this.$el(name);
225
+ }
226
+
227
+ /**
228
+ * Get the current catcher (for debugging)
229
+ */
230
+ getCatcher(): Record<string, any> {
231
+ return { ...this.catcher };
232
+ }
233
+ }
234
+
235
+ /**
236
+ * --- EXPORTS ---
237
+ */
238
+
239
+ export const $ = () => new RuntimeChain(false).proxy;
240
+ export const $t = () => new RuntimeChain(true).proxy;
241
+ export const chain = (useTokens: boolean = false) => new RuntimeChain(useTokens).proxy;
242
+ export default chain;