@melcanz85/chaincss 1.12.3 → 1.12.5

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/browser/index.js CHANGED
@@ -1,2 +1,3 @@
1
- export * from './react-hooks.jsx';
2
- export {$,run,compile,chain,createTokens,responsive,tokens } from './rtt.js';
1
+ // ChainCSS Browser Entry Point
2
+ export { $, run, compile, chain, createTokens, responsive, tokens } from './rtt.js';
3
+ export { useChainStyles, useDynamicChainStyles, useThemeChainStyles, ChainCSSGlobal, withChainStyles, cx } from './react-hooks.js';
package/node/btt.js CHANGED
@@ -5,7 +5,7 @@ const { tokens, createTokens, responsive } = require('../shared/tokens.cjs');
5
5
  const { AtomicOptimizer } = require('./atomic-optimizer');
6
6
 
7
7
  const atomicOptimizer = new AtomicOptimizer({
8
- enabled: false, // default off; turn on via configure()
8
+ enabled: false,
9
9
  alwaysAtomic: [],
10
10
  neverAtomic: ['content', 'animation']
11
11
  });
@@ -18,8 +18,8 @@ const chain = {
18
18
  cssOutput: undefined,
19
19
  catcher: {},
20
20
  cachedValidProperties: [],
21
- classMap: {}, // For atomic CSS class mapping
22
- atomicStats: null, // For atomic optimizer stats
21
+ classMap: {},
22
+ atomicStats: null,
23
23
 
24
24
  initializeProperties() {
25
25
  try {
@@ -28,7 +28,7 @@ const chain = {
28
28
  const data = fs.readFileSync(jsonPath, 'utf8');
29
29
  this.cachedValidProperties = JSON.parse(data);
30
30
  } else {
31
- console.log('CSS properties not cached, will load on first use');
31
+ console.log('⚠️ CSS properties not cached, will load on first use');
32
32
  }
33
33
  } catch (error) {
34
34
  console.error('Error loading CSS properties:', error.message);
@@ -154,31 +154,36 @@ function $(useTokens = true) {
154
154
  };
155
155
  }
156
156
 
157
- // Handle .select() - renamed from $ to avoid conflict
157
+ // Handle .select() - nested selectors
158
158
  if (prop === 'select') {
159
159
  return function(selector) {
160
- const props = {};
161
- const selectorProxy = new Proxy({}, {
162
- get: (target, methodProp) => {
163
- if (methodProp === 'block') {
164
- return function() {
165
- return {
166
- selectors: [selector],
167
- ...props
168
- };
160
+ const nestedStyles = {};
161
+ const nestedHandler = {
162
+ get: (nestedTarget, nestedProp) => {
163
+ if (nestedProp === 'block') {
164
+ return () => {
165
+ if (!catcher.nestedRules) catcher.nestedRules = [];
166
+ catcher.nestedRules.push({
167
+ selector: selector,
168
+ styles: { ...nestedStyles }
169
+ });
170
+ return proxy;
169
171
  };
170
172
  }
171
- return function(value) {
172
- props[methodProp] = resolveToken(value, useTokens);
173
- return selectorProxy;
173
+ return (value) => {
174
+ nestedStyles[nestedProp] = resolveToken(value, useTokens);
175
+ return nestedProxy;
174
176
  };
175
177
  }
176
- });
177
- return selectorProxy;
178
+ };
179
+ const nestedProxy = new Proxy({}, nestedHandler);
180
+ return nestedProxy;
178
181
  };
179
182
  }
180
183
 
181
- // At-Rules
184
+ // ========== AT-RULES ==========
185
+
186
+ // @media
182
187
  if (prop === 'media') {
183
188
  return function(query, callback) {
184
189
  const subChain = $(useTokens);
@@ -193,6 +198,7 @@ function $(useTokens = true) {
193
198
  };
194
199
  }
195
200
 
201
+ // @keyframes
196
202
  if (prop === 'keyframes') {
197
203
  return function(name, callback) {
198
204
  const keyframeContext = { _keyframeSteps: {} };
@@ -228,10 +234,21 @@ function $(useTokens = true) {
228
234
  };
229
235
  }
230
236
 
237
+ // @font-face
231
238
  if (prop === 'fontFace') {
232
239
  return function(callback) {
233
- const subChain = $(useTokens);
234
- const fontProps = callback(subChain).block();
240
+ const fontProps = {};
241
+ const fontHandler = {
242
+ get: (target, fontProp) => {
243
+ return (value) => {
244
+ fontProps[fontProp] = resolveToken(value, useTokens);
245
+ return fontProxy;
246
+ };
247
+ }
248
+ };
249
+ const fontProxy = new Proxy({}, fontHandler);
250
+ callback(fontProxy);
251
+
235
252
  if (!catcher.atRules) catcher.atRules = [];
236
253
  catcher.atRules.push({
237
254
  type: 'font-face',
@@ -241,71 +258,96 @@ function $(useTokens = true) {
241
258
  };
242
259
  }
243
260
 
261
+ // @supports
244
262
  if (prop === 'supports') {
245
263
  return function(condition, callback) {
246
264
  const subChain = $(useTokens);
247
- const styles = callback(subChain).block();
265
+ const result = callback(subChain);
248
266
  if (!catcher.atRules) catcher.atRules = [];
249
267
  catcher.atRules.push({
250
268
  type: 'supports',
251
269
  condition: condition,
252
- styles: styles
270
+ styles: result
253
271
  });
254
272
  return proxy;
255
273
  };
256
274
  }
257
275
 
276
+ // @container
258
277
  if (prop === 'container') {
259
278
  return function(condition, callback) {
260
279
  const subChain = $(useTokens);
261
- const styles = callback(subChain).block();
280
+ const result = callback(subChain);
262
281
  if (!catcher.atRules) catcher.atRules = [];
263
282
  catcher.atRules.push({
264
283
  type: 'container',
265
284
  condition: condition,
266
- styles: styles
285
+ styles: result
267
286
  });
268
287
  return proxy;
269
288
  };
270
289
  }
271
290
 
291
+ // @layer
272
292
  if (prop === 'layer') {
273
293
  return function(name, callback) {
274
294
  const subChain = $(useTokens);
275
- const styles = callback(subChain).block();
295
+ const result = callback(subChain);
276
296
  if (!catcher.atRules) catcher.atRules = [];
277
297
  catcher.atRules.push({
278
298
  type: 'layer',
279
299
  name: name,
280
- styles: styles
300
+ styles: result
281
301
  });
282
302
  return proxy;
283
303
  };
284
304
  }
285
305
 
306
+ // @counter-style
286
307
  if (prop === 'counterStyle') {
287
308
  return function(name, callback) {
288
- const subChain = $(useTokens);
289
- const properties = callback(subChain).block();
309
+ const counterProps = {};
310
+ const counterHandler = {
311
+ get: (target, counterProp) => {
312
+ return (value) => {
313
+ counterProps[counterProp] = resolveToken(value, useTokens);
314
+ return counterProxy;
315
+ };
316
+ }
317
+ };
318
+ const counterProxy = new Proxy({}, counterHandler);
319
+ callback(counterProxy);
320
+
290
321
  if (!catcher.atRules) catcher.atRules = [];
291
322
  catcher.atRules.push({
292
323
  type: 'counter-style',
293
324
  name: name,
294
- properties: properties
325
+ properties: counterProps
295
326
  });
296
327
  return proxy;
297
328
  };
298
329
  }
299
330
 
331
+ // @property
300
332
  if (prop === 'property') {
301
333
  return function(name, callback) {
302
- const subChain = $(useTokens);
303
- const descriptors = callback(subChain).block();
334
+ const propertyDescs = {};
335
+ const propertyHandler = {
336
+ get: (target, descProp) => {
337
+ return (value) => {
338
+ propertyDescs[descProp] = resolveToken(value, useTokens);
339
+ return propertyProxy;
340
+ };
341
+ }
342
+ };
343
+ const propertyProxy = new Proxy({}, propertyHandler);
344
+ callback(propertyProxy);
345
+
304
346
  if (!catcher.atRules) catcher.atRules = [];
305
347
  catcher.atRules.push({
306
348
  type: 'property',
307
349
  name: name,
308
- descriptors: descriptors
350
+ descriptors: propertyDescs
309
351
  });
310
352
  return proxy;
311
353
  };
@@ -326,7 +368,6 @@ function $(useTokens = true) {
326
368
 
327
369
  const proxy = new Proxy({}, handler);
328
370
 
329
- // Async load CSS properties if needed
330
371
  if (chain.cachedValidProperties.length === 0) {
331
372
  chain.getCSSProperties().catch(err => {
332
373
  console.error('Failed to load CSS properties:', err.message);
@@ -336,7 +377,7 @@ function $(useTokens = true) {
336
377
  return proxy;
337
378
  }
338
379
 
339
- // Process at-rules
380
+ // Process at-rules for CSS generation
340
381
  function processAtRule(rule, parentSelectors = null) {
341
382
  let output = '';
342
383
 
@@ -554,8 +595,21 @@ const run = (...args) => {
554
595
  continue;
555
596
  }
556
597
 
598
+ if (key === 'nestedRules' && Array.isArray(value[key])) {
599
+ value[key].forEach(rule => {
600
+ let nestedBody = '';
601
+ for (let prop in rule.styles) {
602
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
603
+ nestedBody += ` ${kebabKey}: ${rule.styles[prop]};\n`;
604
+ }
605
+ if (nestedBody) {
606
+ atRulesOutput += `${value.selectors.join(', ')} ${rule.selector} {\n${nestedBody} }\n`;
607
+ }
608
+ });
609
+ continue;
610
+ }
611
+
557
612
  if (key === 'hover' && typeof value[key] === 'object') {
558
- // Handle hover styles
559
613
  let hoverBody = '';
560
614
  for (let hoverKey in value[key]) {
561
615
  const kebabKey = hoverKey.replace(/([A-Z])/g, '-$1').toLowerCase();
@@ -585,11 +639,11 @@ const run = (...args) => {
585
639
  chain.cssOutput = cssOutput;
586
640
 
587
641
  if (atomicOptimizer.options.enabled) {
588
- const { css, map, stats } = atomicOptimizer.optimize(styleObjs);
589
- chain.cssOutput = css;
590
- chain.classMap = map;
591
- chain.atomicStats = stats;
592
- return css;
642
+ const result = atomicOptimizer.optimize(styleObjs);
643
+ chain.cssOutput = result.css;
644
+ chain.classMap = result.map;
645
+ chain.atomicStats = result.stats;
646
+ return result.css;
593
647
  }
594
648
 
595
649
  return cssOutput;
@@ -622,6 +676,17 @@ const compile = (obj) => {
622
676
  element[prop].forEach(rule => {
623
677
  atRulesCSS += processAtRule(rule, element.selectors);
624
678
  });
679
+ } else if (prop === 'nestedRules' && Array.isArray(element[prop])) {
680
+ element[prop].forEach(rule => {
681
+ let nestedBody = '';
682
+ for (let nestedProp in rule.styles) {
683
+ const kebabKey = nestedProp.replace(/([A-Z])/g, '-$1').toLowerCase();
684
+ nestedBody += ` ${kebabKey}: ${rule.styles[nestedProp]};\n`;
685
+ }
686
+ if (nestedBody) {
687
+ atRulesCSS += `${element.selectors.join(', ')} ${rule.selector} {\n${nestedBody} }\n`;
688
+ }
689
+ });
625
690
  } else if (prop === 'hover' && typeof element[prop] === 'object') {
626
691
  let hoverBody = '';
627
692
  for (let hoverKey in element[prop]) {
@@ -647,11 +712,11 @@ const compile = (obj) => {
647
712
  chain.cssOutput = cssString.trim();
648
713
 
649
714
  if (atomicOptimizer.options.enabled) {
650
- const { css, map, stats } = atomicOptimizer.optimize(collected);
651
- chain.cssOutput = css;
652
- chain.classMap = map;
653
- chain.atomicStats = stats;
654
- return css;
715
+ const result = atomicOptimizer.optimize(collected);
716
+ chain.cssOutput = result.css;
717
+ chain.classMap = result.map;
718
+ chain.atomicStats = result.stats;
719
+ return result.css;
655
720
  }
656
721
 
657
722
  return chain.cssOutput;
@@ -665,11 +730,9 @@ function recipe(options) {
665
730
  compoundVariants = []
666
731
  } = options;
667
732
 
668
- // Store the original style objects
669
733
  const baseStyle = typeof base === 'function' ? base() : base;
670
734
  const variantStyles = {};
671
735
 
672
- // Store variant style objects
673
736
  for (const [variantName, variantMap] of Object.entries(variants)) {
674
737
  variantStyles[variantName] = {};
675
738
  for (const [variantKey, variantStyle] of Object.entries(variantMap)) {
@@ -679,53 +742,12 @@ function recipe(options) {
679
742
  }
680
743
  }
681
744
 
682
- // Store compound variant styles
683
745
  const compoundStyles = compoundVariants.map(cv => ({
684
746
  condition: cv.variants || cv,
685
747
  style: typeof cv.style === 'function' ? cv.style() : cv.style
686
748
  }));
687
749
 
688
- // Helper to extract atomic class names from a style object
689
- function getAtomicClasses(styleObj) {
690
- if (!styleObj) return [];
691
-
692
- const classes = [];
693
-
694
- // Check if atomic optimizer is enabled and has class mapping
695
- if (atomicOptimizer.options.enabled && chain.classMap) {
696
- // Generate a temporary style to get atomic classes
697
- const tempBuilder = $(true);
698
- for (const [prop, value] of Object.entries(styleObj)) {
699
- if (prop !== 'selectors' && prop !== 'hover' && tempBuilder[prop]) {
700
- tempBuilder[prop](value);
701
- }
702
- }
703
-
704
- // Add hover styles if present
705
- if (styleObj.hover) {
706
- tempBuilder.hover();
707
- for (const [hoverProp, hoverValue] of Object.entries(styleObj.hover)) {
708
- if (tempBuilder[hoverProp]) tempBuilder[hoverProp](hoverValue);
709
- }
710
- tempBuilder.end();
711
- }
712
-
713
- // Get the style object to extract atomic classes
714
- const style = tempBuilder.block();
715
-
716
- // Find matching atomic classes from the classMap
717
- // This requires that the atomic optimizer has processed these styles
718
- const selectorKey = JSON.stringify(style);
719
- if (chain.classMap[selectorKey]) {
720
- return chain.classMap[selectorKey].split(' ');
721
- }
722
- }
723
-
724
- return classes;
725
- }
726
-
727
- // Helper to merge style objects and return class names
728
- function mergeStylesToClasses(...styles) {
750
+ function mergeStyles(...styles) {
729
751
  const merged = {};
730
752
  for (const style of styles) {
731
753
  if (!style) continue;
@@ -741,59 +763,18 @@ function recipe(options) {
741
763
  }
742
764
  }
743
765
  }
744
-
745
- // Generate a unique class name for this combination
746
- const selectorKey = Object.entries(merged)
747
- .filter(([k]) => k !== 'selectors' && k !== 'hover')
748
- .map(([k, v]) => `${k}-${v}`)
749
- .join('--');
750
-
751
- const baseClassName = `recipe-${selectorKey}`;
752
-
753
- // Register this style with the atomic optimizer if enabled
754
- if (atomicOptimizer.options.enabled) {
755
- // Create a style object with the merged styles
756
- const styleObj = {
757
- selectors: [`.${baseClassName}`],
758
- ...merged
759
- };
760
-
761
- // Process through atomic optimizer
762
- const { css, map } = atomicOptimizer.optimize({ [baseClassName]: styleObj });
763
-
764
- // Store the generated CSS in chain
765
- if (css) {
766
- chain.cssOutput = (chain.cssOutput || '') + css;
767
- }
768
-
769
- // Return atomic classes if available, otherwise return the generated class
770
- if (map && map[`.${baseClassName}`]) {
771
- return map[`.${baseClassName}`].split(' ');
772
- }
773
- }
774
-
775
- // Fallback: return the generated class name
776
- return [baseClassName];
766
+ return merged;
777
767
  }
778
768
 
779
- // The main pick function that returns class names
780
769
  function pick(variantSelection = {}) {
781
- // Merge defaults with selection
782
770
  const selected = { ...defaultVariants, ...variantSelection };
783
-
784
- // Collect all relevant styles
785
771
  const stylesToMerge = [];
786
772
 
787
- // Add base style
788
773
  if (baseStyle) stylesToMerge.push(baseStyle);
789
-
790
- // Add variant styles
791
774
  for (const [variantName, variantValue] of Object.entries(selected)) {
792
775
  const variantStyle = variantStyles[variantName]?.[variantValue];
793
776
  if (variantStyle) stylesToMerge.push(variantStyle);
794
777
  }
795
-
796
- // Add compound variants
797
778
  for (const cv of compoundStyles) {
798
779
  const matches = Object.entries(cv.condition).every(
799
780
  ([key, value]) => selected[key] === value
@@ -801,19 +782,30 @@ function recipe(options) {
801
782
  if (matches && cv.style) stylesToMerge.push(cv.style);
802
783
  }
803
784
 
804
- // Merge styles and return class names
805
- const classNames = mergeStylesToClasses(...stylesToMerge);
785
+ const merged = mergeStyles(...stylesToMerge);
806
786
 
807
- // Return as string for easy use
808
- return classNames.join(' ');
787
+ const styleBuilder = $(true);
788
+ for (const [prop, value] of Object.entries(merged)) {
789
+ if (prop === 'selectors' || prop === 'hover') continue;
790
+ if (styleBuilder[prop]) styleBuilder[prop](value);
791
+ }
792
+
793
+ if (merged.hover) {
794
+ styleBuilder.hover();
795
+ for (const [hoverProp, hoverValue] of Object.entries(merged.hover)) {
796
+ if (styleBuilder[hoverProp]) styleBuilder[hoverProp](hoverValue);
797
+ }
798
+ styleBuilder.end();
799
+ }
800
+
801
+ const selectors = merged.selectors || [];
802
+ return styleBuilder.block(...selectors);
809
803
  }
810
804
 
811
- // Add metadata for introspection
812
805
  pick.variants = variants;
813
806
  pick.defaultVariants = defaultVariants;
814
807
  pick.base = baseStyle;
815
808
 
816
- // Helper to get all possible variant combinations
817
809
  pick.getAllVariants = () => {
818
810
  const result = [];
819
811
  const variantKeys = Object.keys(variants);
@@ -834,82 +826,35 @@ function recipe(options) {
834
826
  return result;
835
827
  };
836
828
 
837
- // Pre-compile all variants at build time
838
829
  pick.compileAll = () => {
839
830
  const allVariants = pick.getAllVariants();
840
831
  const styles = [];
841
832
 
842
- // Add base
843
833
  if (baseStyle) styles.push(baseStyle);
844
-
845
- // Add all variant styles
846
834
  for (const variantMap of Object.values(variants)) {
847
835
  for (const variantStyle of Object.values(variantMap)) {
848
836
  if (variantStyle) styles.push(variantStyle);
849
837
  }
850
838
  }
851
-
852
- // Add compound variant styles
853
839
  for (const cv of compoundStyles) {
854
840
  if (cv.style) styles.push(cv.style);
855
841
  }
856
842
 
857
- // Compile all styles through atomic optimizer
858
843
  if (atomicOptimizer.options.enabled) {
859
844
  const styleObj = {};
860
845
  styles.forEach((style, i) => {
861
846
  const selectors = style.selectors || [`variant-${i}`];
862
847
  styleObj[selectors[0].replace(/^\./, '')] = style;
863
848
  });
864
- const { css, map } = atomicOptimizer.optimize(styleObj);
865
- chain.cssOutput = (chain.cssOutput || '') + css;
866
- chain.classMap = { ...chain.classMap, ...map };
867
- return css;
849
+ const result = atomicOptimizer.optimize(styleObj);
850
+ chain.cssOutput = (chain.cssOutput || '') + result.css;
851
+ chain.classMap = { ...chain.classMap, ...result.map };
852
+ return result.css;
868
853
  }
869
854
 
870
- // Fallback: use run()
871
855
  return run(...styles);
872
856
  };
873
857
 
874
- // Helper to get CSS for a specific variant combination
875
- pick.getCSS = (variantSelection = {}) => {
876
- const selected = { ...defaultVariants, ...variantSelection };
877
- const stylesToMerge = [];
878
-
879
- if (baseStyle) stylesToMerge.push(baseStyle);
880
- for (const [variantName, variantValue] of Object.entries(selected)) {
881
- const variantStyle = variantStyles[variantName]?.[variantValue];
882
- if (variantStyle) stylesToMerge.push(variantStyle);
883
- }
884
- for (const cv of compoundStyles) {
885
- const matches = Object.entries(cv.condition).every(
886
- ([key, value]) => selected[key] === value
887
- );
888
- if (matches && cv.style) stylesToMerge.push(cv.style);
889
- }
890
-
891
- // Create a temporary style object to generate CSS
892
- const tempBuilder = $(true);
893
- for (const style of stylesToMerge) {
894
- for (const [prop, value] of Object.entries(style)) {
895
- if (prop !== 'selectors' && prop !== 'hover' && tempBuilder[prop]) {
896
- tempBuilder[prop](value);
897
- }
898
- }
899
- if (style.hover) {
900
- tempBuilder.hover();
901
- for (const [hoverProp, hoverValue] of Object.entries(style.hover)) {
902
- if (tempBuilder[hoverProp]) tempBuilder[hoverProp](hoverValue);
903
- }
904
- tempBuilder.end();
905
- }
906
- }
907
-
908
- const mergedStyle = tempBuilder.block();
909
- const css = compile({ [selected.join('-')]: mergedStyle });
910
- return css;
911
- };
912
-
913
858
  return pick;
914
859
  }
915
860
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melcanz85/chaincss",
3
- "version": "1.12.3",
3
+ "version": "1.12.5",
4
4
  "description": "Chainable CSS-in-JS with build-time compilation, atomic CSS, and zero-runtime options",
5
5
  "keywords": [
6
6
  "css",
@@ -31,15 +31,19 @@
31
31
  "import": "./browser/index.js",
32
32
  "require": "./browser/index.js"
33
33
  },
34
+ "node": {
35
+ "import": "./node/btt.js",
36
+ "require": "./node/btt.js"
37
+ },
34
38
  "default": "./browser/index.js"
35
39
  },
36
40
  "./react": {
37
41
  "types": "./types.d.ts",
38
42
  "browser": {
39
- "import": "./browser/react-hooks.jsx",
40
- "require": "./browser/react-hooks.jsx"
43
+ "import": "./browser/react-hooks.js",
44
+ "require": "./browser/react-hooks.js"
41
45
  },
42
- "default": "./browser/react-hooks.jsx"
46
+ "default": "./browser/react-hooks.js"
43
47
  },
44
48
  "./browser/*": {
45
49
  "browser": {
File without changes