@melcanz85/chaincss 1.7.2 → 1.8.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.
Files changed (4) hide show
  1. package/README.md +119 -148
  2. package/chaincss.js +4 -2
  3. package/package.json +1 -1
  4. package/transpiler.js +148 -186
package/README.md CHANGED
@@ -3,17 +3,17 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@melcanz85/chaincss.svg)](https://www.npmjs.com/package/@melcanz85/chaincss)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- **Compile-time CSS-in-JS** - Runs during build, not in the browser!
6
+ **Compile-time CSS-in-JS** - Runs during build, not in the browser!
7
7
 
8
8
  A simple JavaScript-to-CSS transpiler that converts JS objects into optimized CSS **without runtime overhead**.
9
9
 
10
- ## 🚀 Installation
10
+ ## Installation
11
11
 
12
12
  ```bash
13
13
  npm install @melcanz85/chaincss
14
14
  ```
15
15
 
16
- 📦 Usage (Node.js)
16
+ **Usage (Node.js)**
17
17
 
18
18
  Quick Setup
19
19
 
@@ -26,50 +26,68 @@ Quick Setup
26
26
  ### Update your package.json scripts:
27
27
 
28
28
  "scripts": {
29
- "start": "concurrently \"nodemon server.js\" \"nodemon --watch chaincss/*.jcss --watch processor.js --exec 'node processor.js'\""
29
+ "start": "concurrently \"nodemon server.js\" \"nodemon --watch chaincss/*.jcss --watch processor.cjs --exec 'node processor.cjs'\""
30
30
  }
31
31
 
32
32
 
33
- ## 🔧 CSS Prefixing
33
+ ## CSS Prefixing
34
34
 
35
- ChainCSS offers two prefixing modes:
35
+ ChainCSS offers two prefixing approaches:
36
36
 
37
- ### 1. Lightweight Mode (Default, ~50KB)
37
+ ### 1. Built-in Prefixer (Auto Mode, Default)
38
38
 
39
- Built-in prefixer that handles the most common CSS properties:
39
+ Our lightweight built-in prefixer handles the most common CSS properties:
40
40
  - Flexbox & Grid
41
41
  - Transforms & Animations
42
42
  - Filters & Effects
43
43
  - Text effects
44
44
  - Box properties
45
45
 
46
- No additional installation needed!
47
-
48
- ### 2. Full Mode (Uses Autoprefixer)
46
+ **No additional installation needed!** Just run:
47
+ ```bash
48
+ npx chaincss input.jcss output.css --watch
49
+ ```
50
+ ### 2. Full Autoprefixer Mode (Complete Coverage)
49
51
 
50
52
  For complete prefixing coverage of all CSS properties:
51
53
 
52
54
  ```bash
53
55
  npm install autoprefixer postcss browserslist caniuse-db
56
+
57
+ # then run this command
58
+ npx chaincss input.jcss output.css --watch --prefixer-mode full
59
+
54
60
  ```
55
- Project Structure
56
61
 
57
- Create this folder structure in your project:
62
+ ### 3. Make Full Mode Permanent
63
+
64
+ Edit your package.json script:
65
+
66
+ "scripts": {
67
+ "css:watch": "chaincss src/styles.jcss dist/styles.css --watch --prefixer-mode full",
68
+ "start": "concurrently \"npm run dev\" \"npm run css:watch\""
69
+ }
70
+
71
+
72
+ **Project Structure (vanillajs, vanilla nodejs)**
73
+
74
+ Create this folder structure in your vanillaJS project:
58
75
 
59
76
  your-project/
60
77
  ├── chaincss/ # ChainCSS source files
61
78
  │ ├── main.jcss # Main entry file
62
79
  │ ├── chain.jcss # Chaining definitions
63
- │ └── processor.js # Processing script
80
+ │ └── processor.js # Processing script
64
81
  ├── public/ # Output files
65
82
  │ ├── index.html
66
83
  │ └── style.css # Generated CSS
67
84
  ├── node_modules/
68
85
  ├── package.json
69
- └── package-lock.json
86
+ ├── package.lock.json
87
+ └── server.js
70
88
 
71
89
 
72
- The Initialization processor Setup
90
+ **The Initialization processor Setup**
73
91
 
74
92
  In chaincss/processor.js:
75
93
 
@@ -83,107 +101,60 @@ In chaincss/processor.js:
83
101
  process.exit(1);
84
102
  }
85
103
 
86
- 💻 Code Examples
104
+ ## Code Examples
87
105
 
88
106
  //--Chaining File (chaincss/chain.jcss):
89
107
 
90
- ### This is where the chaining happens all codes must be in javascript syntax.
91
- The chain methods are the same as the css properties but in camelCase mode
92
- and the exception of the background property which is shorten to 'bg' only for
93
- example background-color is bgColor() in chaincss. The value of the block()
94
- method is the css selector which is always at the end of the chain or block.
95
-
96
- // Variables for consistent styling
97
- const bodyBg = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
98
- const headerBg = 'rgba(255, 255, 255, 0.95)';
99
- const bodyFontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
100
- Ubuntu, sans-serif";
101
- const headerBoxShadow = '0 2px 20px rgba(0,0,0,0.1)';
102
- const logoFontSize = '1.8rem';
103
-
104
- const reset = chain
105
- .margin('0')
106
- .padding('0')
107
- .boxSizing('border-box')
108
- .block('*');
109
-
110
- const body = chain
111
- .fontFamily(bodyFontFamily)
112
- .lineHeight('1.6')
113
- .color('#1e293b')
114
- .bg(bodyBg)
115
- .block('body');
116
-
117
- /* Header/Navigation */
118
- const navbar = chain
119
- .bg(headerBg)
120
- .backdropFilter('blur(10px)')
121
- .padding('1rem 5%')
122
- .position('fixed')
123
- .width('100%')
124
- .top('0')
125
- .zIndex('1000')
126
- .boxShadow(headerBoxShadow)
108
+ **This is where the chaining happens all codes must be in javascript syntax.
109
+ The chain methods are the same as the css properties but in camelCase mode.
110
+ The value of the block() method is the css selector which is always at the
111
+ end of the chain or block.**
112
+
113
+ /* Header/Navigation */
114
+ const navbar = $().backdropFilter('blur(10px)').padding('1rem 5%')
115
+ .position('fixed').width('100%').top('0').zIndex('1000').boxShadow(headerBoxShadow)
127
116
  .block('.navbar');
128
117
 
129
- const nav_container = chain
130
- .maxWidth('1200px')
131
- .margin('0 auto')
132
- .display('flex')
133
- .justifyContent('space-between')
134
- .alignItems('center')
135
- .block('.nav-container');
136
-
137
- const logo = chain
138
- .fontSize(logoFontSize)
139
- .fontWeight('700')
140
- .bg('linear-gradient(135deg, #667eea, #764ba2)')
141
- .backgroundClip('text')
142
- .textFillColor('transparent')
143
- .letterSpacing('-0.5px')
144
- .block('.logo');
118
+ const nav_container = $().maxWidth('1200px').margin('0 auto').display('flex')
119
+ .justifyContent('space-between').alignItems('center').block('.nav-container');
145
120
 
146
121
  module.exports = {
147
- reset,
148
122
  navbar,
149
- nav_container,
150
- logo
123
+ nav_container
151
124
  };
152
125
 
153
126
 
154
- //--Main File (chaincss/main.jcss):
127
+ **Main File (chaincss/main.jcss):**
155
128
 
156
129
  <@
157
130
  // Import chaining definitions
158
131
  const style = get('./chain.jcss');
159
132
 
160
133
  // Override specific styles
161
- style.logo.fontSize = '2rem';
134
+ style.navbar.padding = '2rem 5%';
162
135
 
163
136
  // Compile to CSS
164
137
  compile(style);
165
138
  @>
166
139
 
167
140
  @keyframes fadeInUp {
168
- <@
169
- run(
170
- chain.opacity('0').transform('translateY(20px)').block('from'),
171
- chain.opacity('1').transform('translateY(0)').block('to'),
172
- );
173
- @>
141
+ <@
142
+ const from = $().opacity('0').transform('translateY(20px)').block('from');
143
+ const to = $().chain.opacity('1').transform('translateY(0)').block('to');
144
+ run(from,to);
145
+ @>
174
146
  }
175
147
 
176
148
  /* Responsive */
177
149
  @media (max-width: 768px) {
178
- <@
179
- run(
180
- chain.fontSize('2.5rem').block('.hero h1'),
181
- chain.flexDirection('column').gap('1rem').block('.stats'),
182
- chain.flexDirection('column').alignItems('center').block('.cta-buttons'),
183
- chain.gridTemplateColumns('1fr').block('.example-container'),
184
- chain.display('none').block('.nav-links')
185
- );
186
- @>
150
+ <@
151
+ const hero_h1 = $().fontSize('2.5rem').block('.hero h1');
152
+ const stats = $().flexDirection('column').gap('1rem').block('.stats');
153
+ const cta_buttons = $().flexDirection('column').alignItems('center').block('.cta-buttons');
154
+ const ex_container = $().gridTemplateColumns('1fr').block('.example-container');
155
+ const nav_links = $().display('none').block('.nav-links');
156
+ run(hero_h1,stats,cta_buttons,ex_container,nav_links);
157
+ @>
187
158
  }
188
159
 
189
160
 
@@ -231,7 +202,7 @@ ChainCSS works great with React and Vite! Here's how to set it up:
231
202
 
232
203
  **src/components/Nav/navbar.jcss**
233
204
 
234
- const navbar = chain
205
+ const navbar = $()
235
206
  .bg('rgba(255, 255, 255, 0.95)')
236
207
  .backdropFilter('blur(10px)')
237
208
  .padding('1rem 5%')
@@ -259,15 +230,11 @@ ChainCSS works great with React and Vite! Here's how to set it up:
259
230
 
260
231
  // You can mix a default style using run() method or even vanilla css (without delimeters)
261
232
  <@
262
- run(
263
- chain.margin('0').padding('0').boxSizing('border-box').block('*'),
264
- chain
265
- .fontFamily("-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif")
266
- .lineHeight('1.6')
267
- .color('#1e293b')
268
- .bg('linear-gradient(135deg, #667eea 0%, #764ba2 100%)')
269
- .block('body')
270
- );
233
+ const reset = $().margin('0').padding('0').boxSizing('border-box').block('*');
234
+ const body = $().fontFamily("-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
235
+ Oxygen, Ubuntu, sans-serif").lineHeight('1.6').color('#1e293b')
236
+ .background('linear-gradient(135deg, #667eea 0%, #764ba2 100%)').block('body');
237
+ run(reset, body);
271
238
  @>
272
239
 
273
240
  .button {
@@ -281,42 +248,44 @@ ChainCSS works great with React and Vite! Here's how to set it up:
281
248
 
282
249
  <@
283
250
  // Import all component styles
284
- const nav = get('./src/components/Navbar/navbar.jcss');
285
- const hero = get('./src/components/Hero/hero.jcss');
286
-
287
- // Merge for one compilation
288
- const allStyles = Object.assign({},nav,hero);
289
-
290
- // Overwrite padding in navbar chain!
291
- nav.navbar.padding = '1rem 5%';
292
-
293
- compile(allStyles);
251
+ const nav = get('./src/components/nav/nav.jcss');
252
+ const hero = get('./src/components/sect_hero/hero.jcss');
253
+ const feature = get('./src/components/sect_feature/feature.jcss');
254
+ const example = get('./src/components/sect_example/example.jcss');
255
+ const gstart = get('./src/components/sect_gStart/gStart.jcss');
256
+ const footer = get('./src/components/footer/footer.jcss');
257
+
258
+ const merged = Object.assign({},nav,hero,feature,example,gstart,footer);
259
+
260
+ // Overwrite your chaining file
261
+ nav.logo.textDecoration = 'none';
262
+ //example.css_output.overflowWrap = 'break-word';
263
+
264
+ compile(merged);
294
265
  @>
295
266
 
296
267
  // you can add keyframes and media queries in this setup
297
268
  @keyframes fadeInUp {
298
269
  <@
299
- run(
300
- chain.opacity('0').transform('translateY(20px)').block('from'),
301
- chain.opacity('1').transform('translateY(0)').block('to'),
302
- );
270
+ const from = $().opacity('0').transform('translateY(20px)').block('from');
271
+ const to = $().opacity('1').transform('translateY(0)').block('to');
272
+ run(from,to);
303
273
  @>
304
- }
274
+ }
305
275
 
306
276
  /* Responsive */
307
277
  @media (max-width: 768px) {
308
278
  <@
309
- run(
310
- chain.fontSize('2.5rem').block('.hero h1'),
311
- chain.flexDirection('column').gap('1rem').block('.stats'),
312
- chain.flexDirection('column').alignItems('center').block('.cta-buttons'),
313
- chain.gridTemplateColumns('1fr').block('.example-container'),
314
- chain.display('none').block('.nav-links')
315
- );
279
+ const hero = $().fontSize('2.5rem').block('.hero h1');
280
+ const stats = $().flexDirection('column').gap('1rem').block('.stats');
281
+ const ctaButtons = $().flexDirection('column').alignItems('center').block('.cta-buttons');
282
+ const exampleContainer = $().gridTemplateColumns('1fr').block('.example-container');
283
+ const navLinks = $().display('none').block('.nav-links');
284
+ run(hero,stats,ctaButtons,exampleContainer,navLinks);
316
285
  @>
317
- }
286
+ }
318
287
 
319
- Benefits of This Approach
288
+ **Benefits of This Approach**
320
289
 
321
290
  ✅ Component Co-location: Styles live next to their components
322
291
 
@@ -328,9 +297,10 @@ Benefits of This Approach
328
297
 
329
298
  ✅ No Redundancy: Styles are automatically merged and optimized
330
299
 
331
- **This structure is much cleaner and follows React best practices! Each component owns its styles, and `main.jcss` simply orchestrates the compilation.**
300
+ This structure is much cleaner and follows React best practices! Each component owns its styles, and
301
+ `main.jcss` simply orchestrates the compilation.
332
302
 
333
- 🎯 Best Practices with React
303
+ ## 🎯 Best Practices with React
334
304
 
335
305
  1. Component-Specific Styles
336
306
 
@@ -360,7 +330,7 @@ Benefits of This Approach
360
330
  .backgroundColor(theme.secondary)
361
331
  .block('.btn:hover');
362
332
 
363
- 📝 Notes
333
+ ## 📝 Notes
364
334
 
365
335
  You can directly put css syntax code on your main.jcss file.
366
336
 
@@ -373,12 +343,12 @@ Benefits of This Approach
373
343
  You can modify your style in between get() and compile() in the
374
344
  main file it will overwrite the styles in the chain file.
375
345
 
376
- 🎨 Editor Support
346
+ ## 🎨 Editor Support
377
347
 
378
348
  Since .jcss files are just JavaScript files with ChainCSS syntax, you can
379
349
  easily enable proper syntax highlighting in your editor:
380
350
 
381
- VS Code
351
+ **VS Code**
382
352
 
383
353
  Add this to your project's .vscode/settings.json:
384
354
 
@@ -388,7 +358,7 @@ Add this to your project's .vscode/settings.json:
388
358
  }
389
359
  }
390
360
 
391
- WebStorm / IntelliJ IDEA
361
+ **WebStorm / IntelliJ IDEA**
392
362
 
393
363
  Go to Settings/Preferences → Editor → File Types
394
364
 
@@ -396,13 +366,13 @@ WebStorm / IntelliJ IDEA
396
366
 
397
367
  Click + and add *.jcss to the registered patterns
398
368
 
399
- Vim / Neovim
369
+ **Vim / Neovim**
400
370
 
401
371
  Add to your .vimrc or init.vim:
402
372
 
403
373
  au BufRead,BufNewFile *.jcss setfiletype javascript
404
374
 
405
- Sublime Text
375
+ **Sublime Text**
406
376
 
407
377
  Create or edit ~/Library/Application Support/Sublime Text/Packages/User/JCSS.sublime-settings:
408
378
 
@@ -413,7 +383,7 @@ json
413
383
  "syntax": "Packages/JavaScript/JavaScript.sublime-syntax"
414
384
  }
415
385
 
416
- Atom
386
+ **Atom**
417
387
 
418
388
  Add to your config.cson:
419
389
  coffeescript
@@ -428,10 +398,11 @@ coffeescript
428
398
 
429
399
  Other Editors
430
400
 
431
- Most modern editors allow you to associate file extensions with language modes. Simply configure your editor to treat .jcss files as JavaScript.
401
+ Most modern editors allow you to associate file extensions with language modes.
402
+ Simply configure your editor to treat .jcss files as JavaScript.
432
403
 
433
404
 
434
- Features
405
+ ## Features
435
406
 
436
407
  Status Feature Description
437
408
 
@@ -440,36 +411,36 @@ Status Feature Description
440
411
  ✅ Vendor prefixing Auto-add -webkit-, -moz-, etc.
441
412
 
442
413
  ✅ Keyframe animations @keyframes support
414
+
415
+ ✅ Media queries responsive @media support
443
416
 
444
417
  ✅ Source maps Debug generated CSS
445
418
 
446
419
  ✅ Watch mode Auto-recompile on file changes
447
420
 
448
421
 
449
- ## 🚀 Key Differentiators
422
+ ### Key Differentiators
450
423
 
451
- - **⚙️ Compile-time, not runtime** - chaincss processes your styles during build, generating pure CSS files. Zero JavaScript execution in the browser means faster page loads!
424
+ - ** Compile-time, not runtime** - chaincss processes your styles during build, generating pure CSS files. Zero JavaScript execution in the browser means faster page loads!
452
425
 
453
- - **🏎️ Performance by design** - Unlike runtime CSS-in-JS libraries, chaincss adds no bundle weight and causes no layout shifts
426
+ - ** Performance by design** - Unlike runtime CSS-in-JS libraries, chaincss adds no bundle weight and causes no layout shifts
454
427
 
455
- - **🔧 Build-time processing** - Your `.jcss` files are transformed before deployment, not in the user's browser
428
+ - ** Build-time processing** - Your `.jcss` files are transformed before deployment, not in the user's browser
456
429
 
457
430
 
458
- ## 🔄 chaincss vs Other Approaches
431
+ ### chaincss vs Other Approaches
459
432
 
460
433
  | Feature | chaincss | Runtime CSS-in-JS | Tailwind | Vanilla CSS |
461
434
  |----------------|-------------------|---------------------|---------------|-------------|
462
- | **When CSS is generated** | ⚙️ **Build time** | 🕒 Runtime (browser)| ⚙️ Build time | 📁 Already written |
463
- | **Browser work**| None - just | Executes JS to | None - just | None |
464
- serves CSS generate CSS serves CSS
465
- | **Dynamic values**| Via JS at | Via props at | ⚠️ Limited | Manual |
466
- build time runtime
467
- | **Bundle size** | Just the CSS | CSS + JS runtime | Just the CSS | Just the CSS |
435
+ | **When CSS is generated** | **Build time** | Runtime (browser) | Build time | Already written |
436
+ | **Browser work**| None - just serves CSS | Executes JS to generate CSS | None - just serves CSS | None |
437
+ | **Dynamic values**| Via JS at build time | Via props at runtime | ⚠ Limited | Manual |
438
+ | **Bundle size** | Just the CSS | CSS + JS runtime | Just the CSS | Just the CSS |
468
439
 
469
- 👨‍💻 Contributing
440
+ ### 👨‍💻 Contributing
470
441
 
471
442
  Contributions are welcome! Feel free to open issues or submit pull requests.
472
443
 
473
- 📄 License
444
+ ### License
474
445
 
475
446
  MIT © Rommel Caneos
package/chaincss.js CHANGED
@@ -17,7 +17,10 @@ let prefixerConfig = {
17
17
  const prefixer = new ChainCSSPrefixer(prefixerConfig);
18
18
 
19
19
  const processScript = (scriptBlock) => {
20
- const context = vm.createContext({ ...transpilerModule});
20
+ const context = vm.createContext({
21
+ ...transpilerModule,
22
+ $: transpilerModule.$
23
+ });
21
24
  const jsCode = scriptBlock.trim();
22
25
  const chainScript = new vm.Script(jsCode);
23
26
  chainScript.runInContext(context);
@@ -173,7 +176,6 @@ function parseArgs(args) {
173
176
  } else if (arg === '--browsers' && args[i + 1]) {
174
177
  result.browsers = args[i + 1].split(',');
175
178
  i++;
176
- // NEW: Add these two
177
179
  } else if (arg === '--no-source-map') {
178
180
  result.sourceMap = false;
179
181
  } else if (arg === '--source-map-inline') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melcanz85/chaincss",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "A simple package transpiler for js to css",
5
5
  "main": "chaincss.js",
6
6
  "publishConfig": {
package/transpiler.js CHANGED
@@ -1,203 +1,159 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
+
3
4
  const chain = {
4
- cssOutput : undefined,
5
+ cssOutput: undefined,
5
6
  catcher: {},
6
-
7
- // Backgrounds
8
- bg(bg){ this.catcher.background = bg; return this; },
9
- bgColor(bgc){ this.catcher.backgroundColor = bgc; return this; },
10
- bgImage(bgi){ this.catcher.backgroundImage = bgi; return this; },
11
- bgRepeat(bgr){ this.catcher.backgroundRepeat = bgr; return this; },
12
- bgAttachment(bga){ this.catcher.backgroundAttachment = bga; return this; },
13
- bgPosition(bgp){ this.catcher.backgroundPosition = bgp; return this; },
14
-
15
- // Border
16
- border(b){ this.catcher.border = b; return this; },
17
- borderStyle(bs){ this.catcher.borderStyle = bs; return this; },
18
- borderWidth(bw){ this.catcher.borderWidth = bw; return this; },
19
- borderColor(bc){ this.catcher.borderColor = bc; return this; },
20
- borderRadius(br){ this.catcher.borderRadius = br; return this; },
21
- borderSideStyle(side, value){
22
- if (side === 'top') {
23
- this.catcher.borderTopStyle = value;
24
- } else if (side === 'right') {
25
- this.catcher.borderRightStyle = value;
26
- } else if (side === 'bottom') {
27
- this.catcher.borderBottomStyle = value;
28
- } else if (side === 'left') {
29
- this.catcher.borderLeftStyle = value;
7
+ cachedValidProperties: [],
8
+
9
+ // Initialize properties synchronously
10
+ initializeProperties() {
11
+ try {
12
+ const jsonPath = path.join(__dirname, 'css-properties.json');
13
+ if (fs.existsSync(jsonPath)) {
14
+ const data = fs.readFileSync(jsonPath, 'utf8');
15
+ this.cachedValidProperties = JSON.parse(data);
16
+
17
+ } else {
18
+ console.log('⚠️ CSS properties not cached, will load on first use');
19
+ }
20
+ } catch (error) {
21
+ console.error('Error loading CSS properties:', error.message);
30
22
  }
31
- return this;
32
23
  },
33
24
 
34
- // Margin
35
- margin(m){ this.catcher.margin = m; return this; },
36
- marginTop(mt){ this.catcher.marginTop = mt; return this; },
37
- marginRight(mr){ this.catcher.marginRight = mr; return this; },
38
- marginBottom(mb){ this.catcher.marginBottom = mb; return this; },
39
- marginLeft(ml){ this.catcher.marginLeft = ml; return this; },
40
-
41
- // Padding
42
- padding(p){ this.catcher.padding = p; return this; },
43
- paddingTop(pt){ this.catcher.paddingTop = pt; return this; },
44
- paddingRight(pr){ this.catcher.paddingRight = pr; return this; },
45
- paddingBottom(pb){ this.catcher.paddingBottom = pb; return this; },
46
- paddingLeft(pl){ this.catcher.paddingLeft = pl; return this; },
47
-
48
- // Height, Width and Max-width
49
- width(w){ this.catcher.width = w; return this; },
50
- minWidth(mnw){ this.catcher.minWidth = mnw; return this; },
51
- maxWidth(mxw){ this.catcher.maxWidth = mxw; return this; },
52
- height(h){ this.catcher.height = h; return this; },
53
- minHeight(mnh){ this.catcher.minHeight = mnh; return this; },
54
- maxHeight(mxh){ this.catcher.maxHeight = mxh; return this; },
55
-
56
- // Outline
57
- outline(o){ this.catcher.outline = o; return this; },
58
- outlineColor(oc){ this.catcher.outlineColor = oc; return this; },
59
- outlineStyle(os){ this.catcher.outlineStyle = os; return this; },
60
- outlineWidth(ow){ this.catcher.outlineWidth = ow; return this; },
61
- outlineOffset(oo){ this.catcher.outlineOffset = oo; return this; },
62
-
63
- // Text
64
- color(c){ this.catcher.color = c; return this; },
65
- direction(d){ this.catcher.direction = d; return this; },
66
- unicodeBidi(ub){ this.catcher.unicodeBidi = ub; return this; },
67
- verticalAlign(va){ this.catcher.verticalAlign = va; return this; },
68
- textTransform(t){ this.catcher.textTransform = t; return this; },
69
- textShadow(s){ this.catcher.textShadow = s; return this; },
70
- textAlign(ta){ this.catcher.textAlign = ta; return this; },
71
- textAlignLast(tal){ this.catcher.textAlignLast = tal; return this; },
72
- textDecoration(value, style){
73
- if (style === undefined) {
74
- this.catcher.textDecoration = value;
75
- } else {
76
- this.catcher.textDecorationStyle = value;
25
+ async getCSSProperties() {
26
+ try {
27
+ const jsonPath = path.join(__dirname, 'css-properties.json');
28
+
29
+ // Check if file already exists
30
+ try {
31
+ await fs.promises.access(jsonPath);
32
+ const existingData = await fs.promises.readFile(jsonPath, 'utf8');
33
+ const objProp = JSON.parse(existingData);
34
+ this.cachedValidProperties = objProp;
35
+ return objProp;
36
+ } catch {
37
+ const url = 'https://raw.githubusercontent.com/mdn/data/main/css/properties.json';
38
+ const response = await fetch(url);
39
+ const data = await response.json();
40
+ const allProperties = Object.keys(data);
41
+
42
+ // Strip vendor prefixes and remove duplicates
43
+ const baseProperties = new Set();
44
+
45
+ allProperties.forEach(prop => {
46
+ // Remove vendor prefixes (-webkit-, -moz-, -ms-, -o-)
47
+ const baseProp = prop.replace(/^-(webkit|moz|ms|o)-/, '');
48
+ baseProperties.add(baseProp);
49
+ });
50
+
51
+ // Convert Set back to array and sort
52
+ const cleanProperties = Array.from(baseProperties).sort();
53
+
54
+ // Save cleaned properties
55
+ await fs.promises.writeFile(jsonPath, JSON.stringify(cleanProperties, null, 2));
56
+
57
+ this.cachedValidProperties = cleanProperties;
58
+ return cleanProperties;
59
+ }
60
+ } catch (error) {
61
+ console.error('Error loading CSS properties:', error.message);
62
+ return [];
77
63
  }
78
- return this;
79
64
  },
80
65
 
81
- // Text-spacing
82
- textIndent(ti){ this.catcher.textIndent = ti; return this; },
83
- letterSpacing(ls){ this.catcher.letterSpacing = ls; return this; },
84
- lineHeight(lh){ this.catcher.lineHeight = lh; return this; },
85
- wordSpacing(ws){ this.catcher.wordSpacing = ws; return this; },
86
- whiteSpace(sws){ this.catcher.whiteSpace = sws; return this; },
87
-
88
- // Font
89
- font(f){ this.catcher.font = f; return this; },
90
- fontFamily(ff){ this.catcher.fontFamily = ff; return this; },
91
- fontStyle(fs){ this.catcher.fontStyle = fs; return this; },
92
- fontWeight(fw){ this.catcher.fontWeight = fw; return this; },
93
- fontVariant(fv){ this.catcher.fontVariant = fv; return this; },
94
- fontSize(fsz){ this.catcher.fontSize = fsz; return this; },
95
-
96
- // List Style
97
- listStyle(ls){ this.catcher.listStyle = ls; return this; },
98
- listStyleType(lst){ this.catcher.listStyleType = lst; return this; },
99
- listStyleImage(lsi){ this.catcher.listStyleImage = lsi; return this; },
100
- listStylePosition(lsp){ this.catcher.listStylePosition = lsp; return this; },
101
-
102
- // Display
103
- display(d){ this.catcher.display = d; return this; },
104
- flex(f){ this.catcher.flex = f; return this; },
105
- alignContent(ac){ this.catcher.alignContent = ac; return this; },
106
- alignSelf(as){ this.catcher.alignSelf = as; return this; },
107
- alignItems(ai){ this.catcher.alignItems = ai; return this; },
108
- justifyContent(jc){ this.catcher.justifyContent = jc; return this; },
109
- flexWrap(fw){ this.catcher.flexWrap = fw; return this; },
110
- flexGrow(fg){ this.catcher.flexGrow = fg; return this; },
111
- flexDirection(fd){ this.catcher.flexDirection = fd; return this; },
112
- order(o){ this.catcher.order = o; return this; },
113
- visibility(v){ this.catcher.visibility = v; return this; },
114
-
115
- // Position
116
- position(p){ this.catcher.position = p; return this; },
117
- top(t){ this.catcher.top = t; return this; },
118
- left(l){ this.catcher.left = l; return this; },
119
- bottom(b){ this.catcher.bottom = b; return this; },
120
-
121
- // Overflow
122
- overflow(o){ this.catcher.overflow = o; return this; },
123
- overflowX(ox){ this.catcher.overflowX = ox; return this; },
124
- overflowY(oy){ this.catcher.overflowY = oy; return this; },
125
- overflowWrap(ow){ this.catcher.overflowWrap = ow; return this; },
66
+ // Synchronous version for internal use
67
+ getCachedProperties() {
68
+ return this.cachedValidProperties;
69
+ }
70
+ };
126
71
 
127
- animation(a){ this.catcher.animation = a; return this; },
128
- textFillColor(tfc){ this.catcher.textFillColor = tfc; return this; },
129
- backgroundClip(bc){ this.catcher.backgroundClip = bc; return this; },
130
- gridTemplateColumns(gtc){ this.catcher.gridTemplateColumns = gtc; return this; },
131
- right(r){ this.catcher.right = r; return this; },
132
- transform(tf){ this.catcher.transform = tf; return this; },
133
- boxShadow(bs){ this.catcher.boxShadow = bs; return this; },
134
- backdropFilter(bf){ this.catcher.backdropFilter = bf; return this; },
135
- gap(g){ this.catcher.gap = g; return this; },
72
+ // Initialize properties synchronously when module loads
73
+ chain.initializeProperties();
136
74
 
137
- // Float
138
- float(f){ this.catcher.float = f; return this; },
139
- clear(c){ this.catcher.clear = c; return this; },
75
+ function $(){
76
+ const catcher = {};
140
77
 
141
- // Z-index
142
- zIndex(zi){ this.catcher.zIndex = zi; return this; },
143
-
144
- // Box-sizing
145
- boxSizing(bs){ this.catcher.boxSizing = bs; return this; },
146
-
147
- // Opacity
148
- opacity(o){ this.catcher.opacity = o; return this; },
78
+ // Use cached properties if available
79
+ const validProperties = chain.cachedValidProperties;
149
80
 
150
- // Transition
151
- transition(t){ this.catcher.transition = t; return this; },
152
-
153
- // Cursor
154
- cursor(c){ this.catcher.cursor = c; return this; },
155
-
156
- content(c){ this.catcher.content = c; return this; },
157
-
158
- accentColor(ac){ this.catcher.accentColor = ac; return this; },
159
-
160
- all(a){ this.catcher.all = a; return this; },
161
-
162
- // Block
163
- block(...args) {
164
- let objectCss = args.length === 0 ? {} : {selectors: args};
165
- Object.assign(objectCss, this.catcher);
166
- this.catcher = {};
167
- return objectCss;
168
- },
169
-
170
- // Navbar
171
- navBar(...args) {
172
- if (arguments.length === 0 && args.length === 0) {
173
- throw new Error('navBar() requires selector argument.');
81
+ const handler = {
82
+ get: (target, prop) => {
83
+ if (prop === 'block') {
84
+ return function(...args) {chain
85
+ // If no args, just return current catcher
86
+ if (args.length === 0) {
87
+ const result = { ...catcher };
88
+ // Clear catcher
89
+ Object.keys(catcher).forEach(key => delete catcher[key]);
90
+ return result;
91
+ }
92
+
93
+ // Create result with selectors
94
+ const result = {
95
+ selectors: args,
96
+ ...catcher
97
+ };
98
+
99
+ // Clear catcher
100
+ Object.keys(catcher).forEach(key => delete catcher[key]);
101
+
102
+ return result;
103
+ };
104
+ }
105
+
106
+ // Convert camelCase to kebab-case for CSS property
107
+ const cssProperty = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
108
+
109
+ // Validate property exists (optional) - use cached properties
110
+ if (validProperties && validProperties.length > 0 && !validProperties.includes(cssProperty)) {
111
+ console.warn(`⚠️ Warning: '${cssProperty}' may not be a valid CSS property`);
112
+ }
113
+
114
+ // Return a function that sets the value
115
+ return function(value) {
116
+ catcher[prop] = value;
117
+ return proxy; // Return proxy for chaining
118
+ };
174
119
  }
175
- let objectResult = {};
176
- objectResult.navUl = this.display('flex').listStyle('none').margin('0').padding('0').block(`${args[0]} ${args[1]}`);
177
- objectResult.navLi = this.margin('0 10px').block(`${args[0]} ${args[1]} ${args[2]}`);
178
- objectResult.navA = this.color('#fff').textDecoration('none').fontWeight('bold').borderRadius('3px').block(`${args[0]} ${args[1]} ${args[2]} ${args[3]}`);
179
- objectResult.navAhover = this.textDecoration('underline').bgColor('#555').block(`${args[0]} ${args[1]} ${args[2]} ${args[3]}:hover`);
180
- return objectResult;
120
+ };
121
+
122
+ // Create the proxy
123
+ const proxy = new Proxy({}, handler);
124
+
125
+ // Trigger async load if needed (don't await)
126
+ if (chain.cachedValidProperties.length === 0) {
127
+ chain.getCSSProperties().catch(err => {
128
+ console.error('Failed to load CSS properties:', err.message);
129
+ });
181
130
  }
131
+
132
+ return proxy;
182
133
  };
183
134
 
184
-
185
135
  const run = (...args) => {
186
- let str1 = '';
187
- args.forEach(
188
- (value)=>{
189
- let str2 = `${value.selectors.toString()} {\n`;
136
+ let cssOutput = '';
137
+
138
+ args.forEach((value) => {
139
+ if (value && value.selectors) {
140
+ let rule = `${value.selectors.join(', ')} {\n`;
141
+
142
+ // Add all properties (excluding 'selectors')
190
143
  for (let key in value) {
191
- if(key !== 'selectors'){
192
- const kebabKey = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
193
- str2 += `\t${kebabKey}: ${value[key]};\n`;
144
+ if (key !== 'selectors' && value.hasOwnProperty(key)) {
145
+ const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
146
+ rule += ` ${kebabKey}: ${value[key]};\n`;
194
147
  }
195
148
  }
196
- str2 += `}\n\n`;
197
- str1 += str2;
149
+
150
+ rule += `}\n\n`;
151
+ cssOutput += rule;
198
152
  }
199
- );
200
- chain.cssOutput = str1.trim();
153
+ });
154
+
155
+ chain.cssOutput = cssOutput.trim();
156
+ return cssOutput.trim();
201
157
  };
202
158
 
203
159
  const compile = (obj) => {
@@ -206,21 +162,23 @@ const compile = (obj) => {
206
162
  for (const key in obj) {
207
163
  if (obj.hasOwnProperty(key)) {
208
164
  const element = obj[key];
209
- let selectors = element.selectors || []; // Provide default empty array if selectors is undefined
165
+ let selectors = element.selectors || [];
210
166
  let elementCSS = '';
167
+
211
168
  for (let prop in element) {
212
169
  if (element.hasOwnProperty(prop) && prop !== 'selectors') {
213
170
  // Convert camelCase to kebab-case
214
- const kebabKey = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
171
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
215
172
  elementCSS += ` ${kebabKey}: ${element[prop]};\n`;
216
173
  }
217
174
  }
218
- selectors = selectors.join();
219
- cssString += `${selectors} {\n${elementCSS}}\n`;
175
+
176
+ cssString += `${selectors.join(', ')} {\n${elementCSS}}\n`;
220
177
  }
221
178
  }
222
179
 
223
- chain.cssOutput = cssString;
180
+ chain.cssOutput = cssString.trim();
181
+ return cssString.trim();
224
182
  };
225
183
 
226
184
  const get = (filename) => {
@@ -228,25 +186,29 @@ const get = (filename) => {
228
186
  if (fileExt !== '.jcss') {
229
187
  throw new Error(`Import error: ${filename} must have .jcss extension`);
230
188
  }
189
+
231
190
  // Try to resolve the path
232
191
  const resolvedPath = path.resolve(process.cwd(), filename);
233
192
 
234
193
  // Check if file exists
235
- const exists = fs.existsSync(resolvedPath);
236
-
237
- if (!exists) {
238
- throw new Error('File not found: '+filename+'(resolved to: '+resolvedPath+')');
194
+ if (!fs.existsSync(resolvedPath)) {
195
+ throw new Error(`File not found: ${filename} (resolved to: ${resolvedPath})`);
239
196
  }
240
197
 
241
198
  return require(resolvedPath);
242
199
  };
243
200
 
201
+ // Make chaincss available globally
244
202
  if (typeof global !== 'undefined') {
245
203
  global.chain = chain;
204
+ global.run = run;
205
+ global.compile = compile;
206
+ global.$ = $;
246
207
  }
247
208
 
248
209
  module.exports = {
249
210
  chain,
211
+ $,
250
212
  run,
251
213
  compile,
252
214
  get