@melcanz85/chaincss 1.9.5 → 1.10.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/README.md +40 -17
- package/browser/index.js +2 -0
- package/{react-hooks.js → browser/react-hooks.jsx} +13 -7
- package/browser/rtt.js +180 -0
- package/{transpiler.js → node/btt.js} +34 -13
- package/{chaincss.js → node/chaincss.js} +1 -1
- package/node/css-properties.json +633 -0
- package/node/index.js +13 -0
- package/{strVal.js → node/strVal.js} +1 -1
- package/package.json +32 -19
- package/shared/tokens.mjs +256 -0
- package/types.d.ts +3 -0
- package/index.js +0 -24
- package/index.react.js +0 -4
- /package/{atomic-optimizer.js → node/atomic-optimizer.js} +0 -0
- /package/{cache-manager.js → node/cache-manager.js} +0 -0
- /package/{prefixer.js → node/prefixer.js} +0 -0
- /package/{tokens.js → shared/tokens.cjs} +0 -0
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@melcanz85/chaincss)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
+
[](https://melcanz08.github.io/chaincss_react_website/)
|
|
8
|
+
|
|
7
9
|
**Write CSS with JavaScript. The only CSS-in-JS library that lets you CHOOSE your runtime cost.**
|
|
8
10
|
|
|
9
11
|
ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful modes** in one package:
|
|
@@ -52,6 +54,7 @@ ChainCSS is a revolutionary CSS-in-JS solution that gives you **two powerful mod
|
|
|
52
54
|
**in your main.jcss**
|
|
53
55
|
|
|
54
56
|
```javascript
|
|
57
|
+
// src/main.jcss
|
|
55
58
|
<@
|
|
56
59
|
const button = get('./button.jcss');
|
|
57
60
|
|
|
@@ -80,27 +83,44 @@ OR with vanilla nodejs project
|
|
|
80
83
|
**Perfect for:** Dynamic styles that respond to props, state, or themes.
|
|
81
84
|
|
|
82
85
|
```jsx
|
|
83
|
-
|
|
84
|
-
import {
|
|
86
|
+
import { useChainStyles, $ } from '@melcanz85/chaincss/react';
|
|
87
|
+
import { useState } from 'react';
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
const SectionDemo = () => {
|
|
90
|
+
const [variant, setVariant] = useState('primary');
|
|
91
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
92
|
+
|
|
93
|
+
// Runtime styles that change based on state/props!
|
|
94
|
+
const styles = useChainStyles(() => ({
|
|
95
|
+
buttonGroup: $()
|
|
96
|
+
.display('flex')
|
|
97
|
+
.gap('1rem')
|
|
98
|
+
.justifyContent('center')
|
|
99
|
+
.marginTop('2rem')
|
|
100
|
+
.flexWrap('wrap')
|
|
101
|
+
.block(),
|
|
102
|
+
|
|
103
|
+
variantButton: $()
|
|
91
104
|
.padding('0.5rem 1rem')
|
|
92
|
-
.
|
|
105
|
+
.backgroundColor('#e2e8f0')
|
|
106
|
+
.border('none')
|
|
107
|
+
.borderRadius('0.375rem')
|
|
108
|
+
.cursor('pointer')
|
|
109
|
+
.fontWeight('500')
|
|
110
|
+
.transition('all 0.2s')
|
|
93
111
|
.hover()
|
|
94
|
-
.
|
|
95
|
-
.boxShadow('0 4px 6px rgba(0,0,0,0.1)')
|
|
112
|
+
.backgroundColor('#cbd5e0')
|
|
96
113
|
.block()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
}),[variant]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<section>
|
|
119
|
+
</section>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export default SectionDemo;
|
|
104
124
|
|
|
105
125
|
|
|
106
126
|
```
|
|
@@ -294,6 +314,9 @@ compile({ hello });" > chaincss/main.jcss
|
|
|
294
314
|
```
|
|
295
315
|
|
|
296
316
|
|
|
317
|
+
See ChainCSS in action! Visit our interactive demo site - [https://melcanz08.github.io/chaincss_react_website/](https://melcanz08.github.io/chaincss_react_website/)
|
|
318
|
+
|
|
319
|
+
|
|
297
320
|
## Performance Comparison
|
|
298
321
|
|
|
299
322
|
Approach Runtime Cost Bundle Size Dynamic Styles Learning Curve
|
package/browser/index.js
ADDED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { $, compile } from './
|
|
2
|
+
import { $, compile, chain } from './rtt';
|
|
3
3
|
|
|
4
4
|
// Cache for generated styles to avoid duplication
|
|
5
5
|
const styleCache = new Map();
|
|
@@ -37,7 +37,7 @@ const updateStyles = (css) => {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
// Main hook for using ChainCSS styles in React
|
|
40
|
-
export function useChainStyles(styles, options = {}) {
|
|
40
|
+
export function useChainStyles(styles, deps = [], options = {}) {
|
|
41
41
|
const {
|
|
42
42
|
cache = true,
|
|
43
43
|
namespace = 'chain',
|
|
@@ -52,10 +52,15 @@ export function useChainStyles(styles, options = {}) {
|
|
|
52
52
|
|
|
53
53
|
// Process styles and generate CSS
|
|
54
54
|
const processed = useMemo(() => {
|
|
55
|
-
|
|
55
|
+
// ✅ FIRST: Resolve styles if it's a function
|
|
56
|
+
const resolvedStyles = typeof styles === 'function' ? styles() : styles;
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
if (!resolvedStyles || Object.keys(resolvedStyles).length === 0) {
|
|
59
|
+
return { classNames: {}, css: '' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ✅ NOW use resolvedStyles for cache key
|
|
63
|
+
const cacheKey = JSON.stringify(resolvedStyles);
|
|
59
64
|
if (cache && styleCache.has(cacheKey)) {
|
|
60
65
|
return styleCache.get(cacheKey);
|
|
61
66
|
}
|
|
@@ -64,7 +69,8 @@ export function useChainStyles(styles, options = {}) {
|
|
|
64
69
|
const newClassNames = {};
|
|
65
70
|
const compiledStyles = {};
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
// ✅ Use resolvedStyles here
|
|
73
|
+
Object.entries(resolvedStyles).forEach(([key, styleDef]) => {
|
|
68
74
|
// Generate a unique class name
|
|
69
75
|
const className = `${namespace}-${key}-${id.current}`;
|
|
70
76
|
|
|
@@ -94,7 +100,7 @@ export function useChainStyles(styles, options = {}) {
|
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
return result;
|
|
97
|
-
}, [styles, namespace]);
|
|
103
|
+
}, [styles, namespace, id.current, ...deps]);
|
|
98
104
|
|
|
99
105
|
// Update the style sheet when styles change
|
|
100
106
|
useEffect(() => {
|
package/browser/rtt.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { tokens, createTokens, responsive } from '../shared/tokens.mjs';
|
|
2
|
+
|
|
3
|
+
let cssProperties = [];
|
|
4
|
+
|
|
5
|
+
// Try to import the JSON file, fallback to fetch if it fails
|
|
6
|
+
try {
|
|
7
|
+
// Dynamic import - will fail gracefully if file doesn't exist
|
|
8
|
+
const module = await import('../node/css-properties.json', {
|
|
9
|
+
assert: { type: 'json' },
|
|
10
|
+
// This prevents the error from breaking the build
|
|
11
|
+
ignore: true
|
|
12
|
+
});
|
|
13
|
+
cssProperties = module.default;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.log('CSS properties file not found, will fetch from CDN');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const chain = {
|
|
19
|
+
cssOutput: undefined,
|
|
20
|
+
catcher: {},
|
|
21
|
+
cachedValidProperties: [],
|
|
22
|
+
|
|
23
|
+
async initializeProperties() {
|
|
24
|
+
if (cssProperties && cssProperties.length > 0) {
|
|
25
|
+
this.cachedValidProperties = cssProperties;
|
|
26
|
+
return;
|
|
27
|
+
}else{
|
|
28
|
+
try {
|
|
29
|
+
let CDNfallBackProp = [];
|
|
30
|
+
const response = await fetch('https://raw.githubusercontent.com/mdn/data/main/css/properties.json');
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
const allProperties = Object.keys(data);
|
|
33
|
+
|
|
34
|
+
// Strip vendor prefixes and remove duplicates
|
|
35
|
+
const baseProperties = new Set();
|
|
36
|
+
allProperties.forEach(prop => {
|
|
37
|
+
const baseProp = prop.replace(/^-(webkit|moz|ms|o)-/, '');
|
|
38
|
+
baseProperties.add(baseProp);
|
|
39
|
+
});
|
|
40
|
+
CDNfallBackProp = Array.from(baseProperties).sort();
|
|
41
|
+
this.cachedValidProperties = CDNfallBackProp;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('Error loading from CDN:', error);
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// Synchronous version for internal use
|
|
50
|
+
getCachedProperties() {
|
|
51
|
+
return this.cachedValidProperties;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Initialize properties synchronously when module loads
|
|
56
|
+
chain.initializeProperties();
|
|
57
|
+
|
|
58
|
+
const resolveToken = (value, useTokens) => {
|
|
59
|
+
if (!useTokens || typeof value !== 'string' || !value.startsWith('$')) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tokenPath = value.slice(1);
|
|
64
|
+
const tokenValue = tokens.get(tokenPath);
|
|
65
|
+
|
|
66
|
+
if (!tokenValue) {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return tokenValue;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function $(useTokens = true){
|
|
74
|
+
const catcher = {};
|
|
75
|
+
|
|
76
|
+
// Use cached properties if available
|
|
77
|
+
const validProperties = chain.cachedValidProperties;
|
|
78
|
+
|
|
79
|
+
const handler = {
|
|
80
|
+
get: (target, prop) => {
|
|
81
|
+
if (prop === 'block') {
|
|
82
|
+
return function(...args) {
|
|
83
|
+
// If no args, just return current catcher
|
|
84
|
+
if (args.length === 0) {
|
|
85
|
+
const result = { ...catcher };
|
|
86
|
+
// Clear catcher
|
|
87
|
+
Object.keys(catcher).forEach(key => delete catcher[key]);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Create result with selectors
|
|
92
|
+
const result = {
|
|
93
|
+
selectors: args,
|
|
94
|
+
...catcher
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Clear catcher
|
|
98
|
+
Object.keys(catcher).forEach(key => delete catcher[key]);
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Convert camelCase to kebab-case for CSS property
|
|
104
|
+
const cssProperty = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
105
|
+
// Validate property exists (optional) - use cached properties
|
|
106
|
+
if (validProperties && validProperties.length > 0 && !validProperties.includes(cssProperty)) {
|
|
107
|
+
console.warn(`Warning: '${cssProperty}' may not be a valid CSS property`);
|
|
108
|
+
}
|
|
109
|
+
// Return a function that sets the value
|
|
110
|
+
return function(value) {
|
|
111
|
+
catcher[prop] = resolveToken(value, useTokens);
|
|
112
|
+
return proxy;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Create the proxy
|
|
118
|
+
const proxy = new Proxy({}, handler);
|
|
119
|
+
|
|
120
|
+
return proxy;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const run = (...args) => {
|
|
124
|
+
let cssOutput = '';
|
|
125
|
+
|
|
126
|
+
args.forEach((value) => {
|
|
127
|
+
if (value && value.selectors) {
|
|
128
|
+
let rule = `${value.selectors.join(', ')} {\n`;
|
|
129
|
+
|
|
130
|
+
// Add all properties (excluding 'selectors')
|
|
131
|
+
for (let key in value) {
|
|
132
|
+
if (key !== 'selectors' && value.hasOwnProperty(key)) {
|
|
133
|
+
const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
134
|
+
rule += ` ${kebabKey}: ${value[key]};\n`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
rule += `}\n\n`;
|
|
139
|
+
cssOutput += rule;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
chain.cssOutput = cssOutput.trim();
|
|
144
|
+
return cssOutput.trim();
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const compile = (obj) => {
|
|
148
|
+
let cssString = '';
|
|
149
|
+
|
|
150
|
+
for (const key in obj) {
|
|
151
|
+
if (obj.hasOwnProperty(key)) {
|
|
152
|
+
const element = obj[key];
|
|
153
|
+
let selectors = element.selectors || [];
|
|
154
|
+
let elementCSS = '';
|
|
155
|
+
|
|
156
|
+
for (let prop in element) {
|
|
157
|
+
if (element.hasOwnProperty(prop) && prop !== 'selectors') {
|
|
158
|
+
// Convert camelCase to kebab-case
|
|
159
|
+
const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
160
|
+
elementCSS += ` ${kebabKey}: ${element[prop]};\n`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
cssString += `${selectors.join(', ')} {\n${elementCSS}}\n`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
chain.cssOutput = cssString.trim();
|
|
169
|
+
return cssString.trim(); // Added return for consistency
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
chain,
|
|
174
|
+
$,
|
|
175
|
+
run,
|
|
176
|
+
compile,
|
|
177
|
+
tokens,
|
|
178
|
+
createTokens,
|
|
179
|
+
responsive
|
|
180
|
+
};
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const
|
|
4
|
-
const tokens =
|
|
3
|
+
const https = require('https'); // Built-in, no install needed!
|
|
4
|
+
const { tokens, createTokens, responsive } = require('../shared/tokens.cjs');
|
|
5
5
|
|
|
6
6
|
const chain = {
|
|
7
7
|
cssOutput: undefined,
|
|
8
8
|
catcher: {},
|
|
9
9
|
cachedValidProperties: [],
|
|
10
10
|
|
|
11
|
-
// Initialize properties synchronously
|
|
12
11
|
initializeProperties() {
|
|
13
12
|
try {
|
|
14
13
|
const jsonPath = path.join(__dirname, 'css-properties.json');
|
|
15
14
|
if (fs.existsSync(jsonPath)) {
|
|
16
15
|
const data = fs.readFileSync(jsonPath, 'utf8');
|
|
17
16
|
this.cachedValidProperties = JSON.parse(data);
|
|
18
|
-
|
|
19
17
|
} else {
|
|
20
18
|
console.log('⚠️ CSS properties not cached, will load on first use');
|
|
21
19
|
}
|
|
@@ -24,6 +22,30 @@ const chain = {
|
|
|
24
22
|
}
|
|
25
23
|
},
|
|
26
24
|
|
|
25
|
+
// Helper function to fetch with https
|
|
26
|
+
fetchWithHttps(url) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
https.get(url, (response) => {
|
|
29
|
+
let data = '';
|
|
30
|
+
|
|
31
|
+
response.on('data', (chunk) => {
|
|
32
|
+
data += chunk;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
response.on('end', () => {
|
|
36
|
+
try {
|
|
37
|
+
const jsonData = JSON.parse(data);
|
|
38
|
+
resolve(jsonData);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
reject(error);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}).on('error', (error) => {
|
|
44
|
+
reject(error);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
|
|
27
49
|
async getCSSProperties() {
|
|
28
50
|
try {
|
|
29
51
|
const jsonPath = path.join(__dirname, 'css-properties.json');
|
|
@@ -36,12 +58,12 @@ const chain = {
|
|
|
36
58
|
this.cachedValidProperties = objProp;
|
|
37
59
|
return objProp;
|
|
38
60
|
} catch {
|
|
61
|
+
// Use https instead of fetch
|
|
39
62
|
const url = 'https://raw.githubusercontent.com/mdn/data/main/css/properties.json';
|
|
40
|
-
const
|
|
41
|
-
const data = await response.json();
|
|
63
|
+
const data = await this.fetchWithHttps(url);
|
|
42
64
|
const allProperties = Object.keys(data);
|
|
43
65
|
|
|
44
|
-
|
|
66
|
+
// Strip vendor prefixes and remove duplicates
|
|
45
67
|
const baseProperties = new Set();
|
|
46
68
|
|
|
47
69
|
allProperties.forEach(prop => {
|
|
@@ -65,7 +87,6 @@ const chain = {
|
|
|
65
87
|
}
|
|
66
88
|
},
|
|
67
89
|
|
|
68
|
-
// Synchronous version for internal use
|
|
69
90
|
getCachedProperties() {
|
|
70
91
|
return this.cachedValidProperties;
|
|
71
92
|
}
|
|
@@ -98,7 +119,7 @@ function $(useTokens = true){
|
|
|
98
119
|
const handler = {
|
|
99
120
|
get: (target, prop) => {
|
|
100
121
|
if (prop === 'block') {
|
|
101
|
-
return function(...args) {
|
|
122
|
+
return function(...args) {
|
|
102
123
|
// If no args, just return current catcher
|
|
103
124
|
if (args.length === 0) {
|
|
104
125
|
const result = { ...catcher };
|
|
@@ -127,7 +148,7 @@ function $(useTokens = true){
|
|
|
127
148
|
}
|
|
128
149
|
// Return a function that sets the value
|
|
129
150
|
return function(value) {
|
|
130
|
-
catcher[prop] = resolveToken(value, useTokens);
|
|
151
|
+
catcher[prop] = resolveToken(value, useTokens);
|
|
131
152
|
return proxy;
|
|
132
153
|
};
|
|
133
154
|
}
|
|
@@ -144,7 +165,7 @@ function $(useTokens = true){
|
|
|
144
165
|
}
|
|
145
166
|
|
|
146
167
|
return proxy;
|
|
147
|
-
}
|
|
168
|
+
}
|
|
148
169
|
|
|
149
170
|
const run = (...args) => {
|
|
150
171
|
let cssOutput = '';
|
|
@@ -199,6 +220,6 @@ module.exports = {
|
|
|
199
220
|
$,
|
|
200
221
|
run,
|
|
201
222
|
compile,
|
|
202
|
-
createTokens
|
|
203
|
-
responsive
|
|
223
|
+
createTokens,
|
|
224
|
+
responsive
|
|
204
225
|
};
|
|
@@ -9,7 +9,7 @@ const fileCache = new Map();
|
|
|
9
9
|
const strVal = require('./strVal.js');
|
|
10
10
|
const { AtomicOptimizer } = require('./atomic-optimizer');
|
|
11
11
|
const { CacheManager } = require('./cache-manager');
|
|
12
|
-
const { $, run, compile: originalCompile, chain } = require('./
|
|
12
|
+
const { $, run, compile: originalCompile, chain } = require('./btt');
|
|
13
13
|
|
|
14
14
|
// Atomic optimizer instance (will be initialized after config)
|
|
15
15
|
let atomicOptimizer = null;
|