@melcanz85/chaincss 1.11.0 → 1.11.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.
package/node/btt.js CHANGED
@@ -1,8 +1,7 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
- const https = require('https'); // Built-in, no install needed!
3
+ const https = require('https');
4
4
  const { tokens, createTokens, responsive } = require('../shared/tokens.cjs');
5
-
6
5
  const chain = {
7
6
  cssOutput: undefined,
8
7
  catcher: {},
@@ -22,35 +21,25 @@ const chain = {
22
21
  }
23
22
  },
24
23
 
25
- // Helper function to fetch with https
26
24
  fetchWithHttps(url) {
27
25
  return new Promise((resolve, reject) => {
28
26
  https.get(url, (response) => {
29
27
  let data = '';
30
-
31
- response.on('data', (chunk) => {
32
- data += chunk;
33
- });
34
-
28
+ response.on('data', (chunk) => data += chunk);
35
29
  response.on('end', () => {
36
30
  try {
37
- const jsonData = JSON.parse(data);
38
- resolve(jsonData);
31
+ resolve(JSON.parse(data));
39
32
  } catch (error) {
40
33
  reject(error);
41
34
  }
42
35
  });
43
- }).on('error', (error) => {
44
- reject(error);
45
- });
36
+ }).on('error', reject);
46
37
  });
47
38
  },
48
39
 
49
40
  async getCSSProperties() {
50
41
  try {
51
42
  const jsonPath = path.join(__dirname, 'css-properties.json');
52
-
53
- // Check if file already exists
54
43
  try {
55
44
  await fs.promises.access(jsonPath);
56
45
  const existingData = await fs.promises.readFile(jsonPath, 'utf8');
@@ -58,26 +47,16 @@ const chain = {
58
47
  this.cachedValidProperties = objProp;
59
48
  return objProp;
60
49
  } catch {
61
- // Use https instead of fetch
62
50
  const url = 'https://raw.githubusercontent.com/mdn/data/main/css/properties.json';
63
51
  const data = await this.fetchWithHttps(url);
64
52
  const allProperties = Object.keys(data);
65
-
66
- // Strip vendor prefixes and remove duplicates
67
53
  const baseProperties = new Set();
68
-
69
54
  allProperties.forEach(prop => {
70
- // Remove vendor prefixes (-webkit-, -moz-, -ms-, -o-)
71
55
  const baseProp = prop.replace(/^-(webkit|moz|ms|o)-/, '');
72
56
  baseProperties.add(baseProp);
73
57
  });
74
-
75
- // Convert Set back to array and sort
76
58
  const cleanProperties = Array.from(baseProperties).sort();
77
-
78
- // Save cleaned properties
79
59
  await fs.promises.writeFile(jsonPath, JSON.stringify(cleanProperties, null, 2));
80
-
81
60
  this.cachedValidProperties = cleanProperties;
82
61
  return cleanProperties;
83
62
  }
@@ -91,130 +70,415 @@ const chain = {
91
70
  return this.cachedValidProperties;
92
71
  }
93
72
  };
94
-
95
- // Initialize properties synchronously when module loads
96
73
  chain.initializeProperties();
97
-
98
74
  const resolveToken = (value, useTokens) => {
99
75
  if (!useTokens || typeof value !== 'string' || !value.startsWith('$')) {
100
76
  return value;
101
77
  }
102
-
103
78
  const tokenPath = value.slice(1);
104
79
  const tokenValue = tokens.get(tokenPath);
105
-
106
- if (!tokenValue) {
107
- return value;
108
- }
109
-
110
- return tokenValue;
80
+ return tokenValue || value;
111
81
  };
112
-
113
- function $(useTokens = true){
82
+ function $(useTokens = true) {
114
83
  const catcher = {};
115
-
116
- // Use cached properties if available
117
84
  const validProperties = chain.cachedValidProperties;
118
-
119
85
  const handler = {
120
86
  get: (target, prop) => {
121
87
  if (prop === 'block') {
122
88
  return function(...args) {
123
- // If no args, just return current catcher
124
89
  if (args.length === 0) {
125
90
  const result = { ...catcher };
126
- // Clear catcher
127
91
  Object.keys(catcher).forEach(key => delete catcher[key]);
128
92
  return result;
129
93
  }
130
-
131
- // Create result with selectors
132
94
  const result = {
133
95
  selectors: args,
134
96
  ...catcher
135
97
  };
136
-
137
- // Clear catcher
138
98
  Object.keys(catcher).forEach(key => delete catcher[key]);
139
-
140
99
  return result;
141
100
  };
142
101
  }
143
- // Convert camelCase to kebab-case for CSS property
144
- const cssProperty = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
145
- // Validate property exists (optional) - use cached properties
102
+ if (prop === '$') {
103
+ return function(selector) {
104
+ const props = {};
105
+ const selectorProxy = new Proxy({}, {
106
+ get: (target, methodProp) => {
107
+ if (methodProp === 'block') {
108
+ return function() {
109
+ return {
110
+ selectors: [selector],
111
+ ...props
112
+ };
113
+ };
114
+ }
115
+ return function(value) {
116
+ props[methodProp] = resolveToken(value, useTokens);
117
+ return selectorProxy; // Return the proxy for chaining
118
+ };
119
+ }
120
+ });
121
+
122
+ return selectorProxy;
123
+ };
124
+ }
125
+ if (prop === 'media') {
126
+ return function(query, callback) {
127
+ const subChain = $(useTokens);
128
+ const result = callback(subChain);
129
+ if (!catcher.atRules) catcher.atRules = [];
130
+ catcher.atRules.push({
131
+ type: 'media',
132
+ query: query,
133
+ styles: Array.isArray(result) ? result : [result]
134
+ });
135
+ return proxy;
136
+ };
137
+ }
138
+ if (prop === 'keyframes') {
139
+ return function(name, callback) {
140
+ const keyframeContext = {
141
+ _keyframeSteps: {}
142
+ };
143
+ const keyframeProxy = new Proxy(keyframeContext, {
144
+ get: (target, stepProp) => {
145
+ if (stepProp === 'from' || stepProp === 'to' || stepProp === 'percent') {
146
+ return function(...args) {
147
+ if (stepProp === 'percent') {
148
+ const value = args[0];
149
+ const stepCallback = args[1];
150
+ const subChain = $(useTokens);
151
+ const properties = stepCallback(subChain).block();
152
+ target._keyframeSteps[`${value}%`] = properties;
153
+ } else {
154
+ const stepCallback = args[0];
155
+ const subChain = $(useTokens);
156
+ const properties = stepCallback(subChain).block();
157
+ target._keyframeSteps[stepProp] = properties;
158
+ }
159
+ return keyframeProxy;
160
+ };
161
+ }
162
+ return undefined;
163
+ }
164
+ });
165
+ callback(keyframeProxy);
166
+ if (!catcher.atRules) catcher.atRules = [];
167
+ catcher.atRules.push({
168
+ type: 'keyframes',
169
+ name: name,
170
+ steps: keyframeContext._keyframeSteps,
171
+ atomic: false
172
+ });
173
+
174
+ return proxy;
175
+ };
176
+ }
177
+ if (prop === 'fontFace') {
178
+ return function(callback) {
179
+ const subChain = $(useTokens);
180
+ const fontProps = callback(subChain).block();
181
+ if (!catcher.atRules) catcher.atRules = [];
182
+ catcher.atRules.push({
183
+ type: 'font-face',
184
+ properties: fontProps,
185
+ atomic: false
186
+ });
187
+
188
+ return proxy;
189
+ };
190
+ }
191
+ if (prop === 'supports') {
192
+ return function(condition, callback) {
193
+ const subChain = $(useTokens);
194
+ const styles = callback(subChain).block();
195
+ if (!catcher.atRules) catcher.atRules = [];
196
+ catcher.atRules.push({
197
+ type: 'supports',
198
+ condition: condition,
199
+ styles: styles
200
+ });
201
+ return proxy;
202
+ };
203
+ }
204
+ if (prop === 'container') {
205
+ return function(condition, callback) {
206
+ const subChain = $(useTokens);
207
+ const styles = callback(subChain).block();
208
+ if (!catcher.atRules) catcher.atRules = [];
209
+ catcher.atRules.push({
210
+ type: 'container',
211
+ condition: condition,
212
+ styles: styles
213
+ });
214
+ return proxy;
215
+ };
216
+ }
217
+ if (prop === 'layer') {
218
+ return function(name, callback) {
219
+ const subChain = $(useTokens);
220
+ const styles = callback(subChain).block();
221
+ if (!catcher.atRules) catcher.atRules = [];
222
+ catcher.atRules.push({
223
+ type: 'layer',
224
+ name: name,
225
+ styles: styles
226
+ });
227
+ return proxy;
228
+ };
229
+ }
230
+ if (prop === 'counterStyle') {
231
+ return function(name, callback) {
232
+ const subChain = $(useTokens);
233
+ const properties = callback(subChain).block();
234
+ if (!catcher.atRules) catcher.atRules = [];
235
+ catcher.atRules.push({
236
+ type: 'counter-style',
237
+ name: name,
238
+ properties: properties,
239
+ atomic: false
240
+ });
241
+ return proxy;
242
+ };
243
+ }
244
+ if (prop === 'property') {
245
+ return function(name, callback) {
246
+ const subChain = $(useTokens);
247
+ const descriptors = callback(subChain).block();
248
+ if (!catcher.atRules) catcher.atRules = [];
249
+ catcher.atRules.push({
250
+ type: 'property',
251
+ name: name,
252
+ descriptors: descriptors,
253
+ atomic: false
254
+ });
255
+ return proxy;
256
+ };
257
+ }
258
+ if (prop === 'from' || prop === 'to' || prop === 'percent') {
259
+ return function(...args) {
260
+ // Handle percent case: .percent(50, callback)
261
+ if (prop === 'percent') {
262
+ const value = args[0];
263
+ const callback = args[1];
264
+ const subChain = $(useTokens);
265
+ const properties = callback(subChain).block();
266
+ if (!this._keyframeSteps) this._keyframeSteps = {};
267
+ this._keyframeSteps[`${value}%`] = properties;
268
+ return this; // Return this for chaining
269
+ }
270
+ const callback = args[0];
271
+ const subChain = $(useTokens);
272
+ const properties = callback(subChain).block();
273
+ if (!this._keyframeSteps) this._keyframeSteps = {};
274
+ this._keyframeSteps[prop] = properties;
275
+ return this;
276
+ };
277
+ }
278
+ const cssProperty = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
146
279
  if (validProperties && validProperties.length > 0 && !validProperties.includes(cssProperty)) {
147
280
  console.warn(`Warning: '${cssProperty}' may not be a valid CSS property`);
148
281
  }
149
- // Return a function that sets the value
150
282
  return function(value) {
151
283
  catcher[prop] = resolveToken(value, useTokens);
152
284
  return proxy;
153
285
  };
154
286
  }
155
287
  };
156
-
157
- // Create the proxy
158
288
  const proxy = new Proxy({}, handler);
159
-
160
- // Trigger async load if needed (don't await)
161
289
  if (chain.cachedValidProperties.length === 0) {
162
290
  chain.getCSSProperties().catch(err => {
163
291
  console.error('Failed to load CSS properties:', err.message);
164
292
  });
165
293
  }
166
-
167
294
  return proxy;
168
295
  }
169
-
170
296
  const run = (...args) => {
171
297
  let cssOutput = '';
172
-
173
298
  args.forEach((value) => {
174
- if (value && value.selectors) {
175
- let rule = `${value.selectors.join(', ')} {\n`;
176
-
177
- // Add all properties (excluding 'selectors')
299
+ if (!value) return;
300
+ if (value.selectors) {
301
+ let mainRuleBody = '';
302
+ let atRulesOutput = '';
178
303
  for (let key in value) {
179
- if (key !== 'selectors' && value.hasOwnProperty(key)) {
304
+ if (key === 'selectors' || !value.hasOwnProperty(key)) continue;
305
+ if (key === 'atRules' && Array.isArray(value[key])) {
306
+ value[key].forEach(rule => {
307
+ atRulesOutput += processAtRule(rule, value.selectors);
308
+ });
309
+ continue;
310
+ }
311
+ else {
180
312
  const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
181
- rule += ` ${kebabKey}: ${value[key]};\n`;
313
+ mainRuleBody += ` ${kebabKey}: ${value[key]};\n`;
182
314
  }
183
315
  }
184
-
185
- rule += `}\n\n`;
186
- cssOutput += rule;
316
+ if (mainRuleBody.trim()) {
317
+ cssOutput += `${value.selectors.join(', ')} {\n${mainRuleBody}}\n`;
318
+ }
319
+ cssOutput += atRulesOutput;
320
+ }
321
+ else if (value.type) {
322
+ cssOutput += processStandaloneAtRule(value);
187
323
  }
188
324
  });
189
-
190
- chain.cssOutput = cssOutput.trim();
191
- return cssOutput.trim();
325
+ cssOutput = cssOutput.replace(/\n{3,}/g, '\n\n').trim();
326
+ chain.cssOutput = cssOutput;
327
+ return cssOutput;
192
328
  };
193
-
329
+ function processAtRule(rule, parentSelectors = null) {
330
+ let output = '';
331
+ switch(rule.type) {
332
+ case 'media':
333
+ if (parentSelectors) {
334
+ let mediaBody = '';
335
+ if (rule.styles) {
336
+ for (let prop in rule.styles) {
337
+ if (prop !== 'selectors' && rule.styles.hasOwnProperty(prop)) {
338
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
339
+ mediaBody += ` ${kebabKey}: ${rule.styles[prop]};\n`;
340
+ }
341
+ }
342
+ }
343
+ if (mediaBody.trim()) {
344
+ output = `@media ${rule.query} {\n ${parentSelectors.join(', ')} {\n${mediaBody} }\n}\n`;
345
+ }
346
+ } else {
347
+ output = `@media ${rule.query} {\n`;
348
+ if (Array.isArray(rule.styles)) {
349
+ rule.styles.forEach(styleObj => {
350
+ if (styleObj.selectors) {
351
+ let ruleBody = '';
352
+ for (let prop in styleObj) {
353
+ if (prop !== 'selectors' && styleObj.hasOwnProperty(prop)) {
354
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
355
+ ruleBody += ` ${kebabKey}: ${styleObj[prop]};\n`;
356
+ }
357
+ }
358
+ if (ruleBody.trim()) {
359
+ output += ` ${styleObj.selectors.join(', ')} {\n${ruleBody} }\n`;
360
+ }
361
+ }
362
+ });
363
+ }
364
+ else if (rule.styles && rule.styles.selectors) {
365
+ let ruleBody = '';
366
+ for (let prop in rule.styles) {
367
+ if (prop !== 'selectors' && rule.styles.hasOwnProperty(prop)) {
368
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
369
+ ruleBody += ` ${kebabKey}: ${rule.styles[prop]};\n`;
370
+ }
371
+ }
372
+ if (ruleBody.trim()) {
373
+ output += ` ${rule.styles.selectors.join(', ')} {\n${ruleBody} }\n`;
374
+ }
375
+ }
376
+ output += '}\n';
377
+ }
378
+ break;
379
+ case 'keyframes':
380
+ output = `@keyframes ${rule.name} {\n`;
381
+ for (let step in rule.steps) {
382
+ output += ` ${step} {\n`;
383
+ for (let prop in rule.steps[step]) {
384
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
385
+ output += ` ${kebabKey}: ${rule.steps[step][prop]};\n`;
386
+ }
387
+ output += ' }\n';
388
+ }
389
+ output += '}\n';
390
+ break;
391
+ case 'font-face':
392
+ output = '@font-face {\n';
393
+ for (let prop in rule.properties) {
394
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
395
+ output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
396
+ }
397
+ output += '}\n';
398
+ break;
399
+ }
400
+ return output;
401
+ }
402
+ function processStandaloneAtRule(rule) {
403
+ let output = '';
404
+ switch(rule.type) {
405
+ case 'font-face':
406
+ output = '@font-face {\n';
407
+ for (let prop in rule.properties) {
408
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
409
+ output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
410
+ }
411
+ output += '}\n';
412
+ break;
413
+ case 'keyframes':
414
+ output = `@keyframes ${rule.name} {\n`;
415
+ for (let step in rule.steps) {
416
+ output += ` ${step} {\n`;
417
+ for (let prop in rule.steps[step]) {
418
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
419
+ output += ` ${kebabKey}: ${rule.steps[step][prop]};\n`;
420
+ }
421
+ output += ' }\n';
422
+ }
423
+ output += '}\n';
424
+ break;
425
+ case 'counter-style':
426
+ output = `@counter-style ${rule.name} {\n`;
427
+ for (let prop in rule.properties) {
428
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
429
+ output += ` ${kebabKey}: ${rule.properties[prop]};\n`;
430
+ }
431
+ output += '}\n';
432
+ break;
433
+ case 'property':
434
+ output = `@property ${rule.name} {\n`;
435
+ for (let desc in rule.descriptors) {
436
+ const kebabKey = desc.replace(/([A-Z])/g, '-$1').toLowerCase();
437
+ output += ` ${kebabKey}: ${rule.descriptors[desc]};\n`;
438
+ }
439
+ output += '}\n';
440
+ break;
441
+ }
442
+ return output;
443
+ }
194
444
  const compile = (obj) => {
195
445
  let cssString = '';
196
-
197
446
  for (const key in obj) {
198
447
  if (obj.hasOwnProperty(key)) {
199
448
  const element = obj[key];
200
- let selectors = element.selectors || [];
201
- let elementCSS = '';
202
-
203
- for (let prop in element) {
204
- if (element.hasOwnProperty(prop) && prop !== 'selectors') {
205
- // Convert camelCase to kebab-case
206
- const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
207
- elementCSS += ` ${kebabKey}: ${element[prop]};\n`;
449
+ if (element.atRules && Array.isArray(element.atRules)) {
450
+ element.atRules.forEach(rule => {
451
+ cssString += processAtRule(rule, null);
452
+ });
453
+ continue;
454
+ }
455
+ if (element.selectors) {
456
+ let elementCSS = '';
457
+ let atRulesCSS = '';
458
+ for (let prop in element) {
459
+ if (prop === 'selectors' || !element.hasOwnProperty(prop)) continue;
460
+ if (prop === 'atRules' && Array.isArray(element[prop])) {
461
+ element[prop].forEach(rule => {
462
+ atRulesCSS += processAtRule(rule, element.selectors);
463
+ });
464
+ } else {
465
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
466
+ elementCSS += ` ${kebabKey}: ${element[prop]};\n`;
467
+ }
208
468
  }
469
+ if (elementCSS.trim()) {
470
+ cssString += `${element.selectors.join(', ')} {\n${elementCSS}}\n`;
471
+ }
472
+ cssString += atRulesCSS;
473
+ }
474
+ else {
475
+ console.log(`Warning: Unknown element type for key "${key}":`, element);
209
476
  }
210
-
211
- cssString += `${selectors.join(', ')} {\n${elementCSS}}\n`;
212
477
  }
213
478
  }
214
-
215
479
  chain.cssOutput = cssString.trim();
480
+ return chain.cssOutput;
216
481
  };
217
-
218
482
  module.exports = {
219
483
  chain,
220
484
  $,
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
-
4
3
  class CacheManager {
5
4
  constructor(cachePath = './.chaincss-cache') {
6
5
  this.cachePath = path.resolve(process.cwd(), cachePath);
@@ -8,8 +7,6 @@ class CacheManager {
8
7
  this.cache = {};
9
8
  this.load();
10
9
  }
11
-
12
- // Load cache from disk
13
10
  load() {
14
11
  try {
15
12
  if (fs.existsSync(this.cachePath)) {
@@ -33,18 +30,12 @@ class CacheManager {
33
30
  this.cache = {};
34
31
  }
35
32
  }
36
-
37
- // Get value from cache
38
33
  get(key) {
39
34
  return this.cache[key];
40
35
  }
41
-
42
- // Set value in cache
43
36
  set(key, value) {
44
37
  this.cache[key] = value;
45
38
  }
46
-
47
- // Save cache to disk
48
39
  save() {
49
40
  try {
50
41
  const data = JSON.stringify(this.cache, null, 2);
@@ -54,8 +45,6 @@ class CacheManager {
54
45
  console.warn('Could not save cache:', error.message);
55
46
  }
56
47
  }
57
-
58
- // Clear cache
59
48
  clear() {
60
49
  this.cache = {};
61
50
  if (fs.existsSync(this.cachePath)) {
@@ -64,5 +53,4 @@ class CacheManager {
64
53
  console.log('Cache cleared');
65
54
  }
66
55
  }
67
-
68
56
  module.exports = { CacheManager };