@melcanz85/chaincss 1.7.3 → 1.9.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.
package/transpiler.js CHANGED
@@ -1,203 +1,173 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
+ const tokenModule = require('./tokens');
4
+ const tokens = tokenModule.tokens;
5
+
3
6
  const chain = {
4
- cssOutput : undefined,
7
+ cssOutput: undefined,
5
8
  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;
9
+ cachedValidProperties: [],
10
+
11
+ // Initialize properties synchronously
12
+ initializeProperties() {
13
+ try {
14
+ const jsonPath = path.join(__dirname, 'css-properties.json');
15
+ if (fs.existsSync(jsonPath)) {
16
+ const data = fs.readFileSync(jsonPath, 'utf8');
17
+ this.cachedValidProperties = JSON.parse(data);
18
+
19
+ } else {
20
+ console.log('⚠️ CSS properties not cached, will load on first use');
21
+ }
22
+ } catch (error) {
23
+ console.error('Error loading CSS properties:', error.message);
30
24
  }
31
- return this;
32
25
  },
33
26
 
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;
27
+ async getCSSProperties() {
28
+ try {
29
+ const jsonPath = path.join(__dirname, 'css-properties.json');
30
+
31
+ // Check if file already exists
32
+ try {
33
+ await fs.promises.access(jsonPath);
34
+ const existingData = await fs.promises.readFile(jsonPath, 'utf8');
35
+ const objProp = JSON.parse(existingData);
36
+ this.cachedValidProperties = objProp;
37
+ return objProp;
38
+ } catch {
39
+ const url = 'https://raw.githubusercontent.com/mdn/data/main/css/properties.json';
40
+ const response = await fetch(url);
41
+ const data = await response.json();
42
+ const allProperties = Object.keys(data);
43
+
44
+ // Strip vendor prefixes and remove duplicates
45
+ const baseProperties = new Set();
46
+
47
+ allProperties.forEach(prop => {
48
+ // Remove vendor prefixes (-webkit-, -moz-, -ms-, -o-)
49
+ const baseProp = prop.replace(/^-(webkit|moz|ms|o)-/, '');
50
+ baseProperties.add(baseProp);
51
+ });
52
+
53
+ // Convert Set back to array and sort
54
+ const cleanProperties = Array.from(baseProperties).sort();
55
+
56
+ // Save cleaned properties
57
+ await fs.promises.writeFile(jsonPath, JSON.stringify(cleanProperties, null, 2));
58
+
59
+ this.cachedValidProperties = cleanProperties;
60
+ return cleanProperties;
61
+ }
62
+ } catch (error) {
63
+ console.error('Error loading CSS properties:', error.message);
64
+ return [];
77
65
  }
78
- return this;
79
66
  },
80
67
 
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; },
68
+ // Synchronous version for internal use
69
+ getCachedProperties() {
70
+ return this.cachedValidProperties;
71
+ }
72
+ };
126
73
 
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; },
74
+ // Initialize properties synchronously when module loads
75
+ chain.initializeProperties();
136
76
 
137
- // Float
138
- float(f){ this.catcher.float = f; return this; },
139
- clear(c){ this.catcher.clear = c; return this; },
77
+ const resolveToken = (value, useTokens) => {
78
+ if (!useTokens || typeof value !== 'string' || !value.startsWith('$')) {
79
+ return value;
80
+ }
140
81
 
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; },
82
+ const tokenPath = value.slice(1);
83
+ const tokenValue = tokens.get(tokenPath);
149
84
 
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
- },
85
+ if (!tokenValue) {
86
+ return value;
87
+ }
88
+
89
+ return tokenValue;
90
+ };
169
91
 
170
- // Navbar
171
- navBar(...args) {
172
- if (arguments.length === 0 && args.length === 0) {
173
- throw new Error('navBar() requires selector argument.');
92
+ function $(useTokens = true){
93
+ const catcher = {};
94
+
95
+ // Use cached properties if available
96
+ const validProperties = chain.cachedValidProperties;
97
+
98
+ const handler = {
99
+ get: (target, prop) => {
100
+ if (prop === 'block') {
101
+ return function(...args) {chain
102
+ // If no args, just return current catcher
103
+ if (args.length === 0) {
104
+ const result = { ...catcher };
105
+ // Clear catcher
106
+ Object.keys(catcher).forEach(key => delete catcher[key]);
107
+ return result;
108
+ }
109
+
110
+ // Create result with selectors
111
+ const result = {
112
+ selectors: args,
113
+ ...catcher
114
+ };
115
+
116
+ // Clear catcher
117
+ Object.keys(catcher).forEach(key => delete catcher[key]);
118
+
119
+ return result;
120
+ };
121
+ }
122
+ // Convert camelCase to kebab-case for CSS property
123
+ const cssProperty = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
124
+ // Validate property exists (optional) - use cached properties
125
+ if (validProperties && validProperties.length > 0 && !validProperties.includes(cssProperty)) {
126
+ console.warn(`Warning: '${cssProperty}' may not be a valid CSS property`);
127
+ }
128
+ // Return a function that sets the value
129
+ return function(value) {
130
+ catcher[prop] = resolveToken(value, useTokens); // ← USE IT HERE
131
+ return proxy;
132
+ };
174
133
  }
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;
134
+ };
135
+
136
+ // Create the proxy
137
+ const proxy = new Proxy({}, handler);
138
+
139
+ // Trigger async load if needed (don't await)
140
+ if (chain.cachedValidProperties.length === 0) {
141
+ chain.getCSSProperties().catch(err => {
142
+ console.error('Failed to load CSS properties:', err.message);
143
+ });
181
144
  }
145
+
146
+ return proxy;
182
147
  };
183
148
 
184
-
185
149
  const run = (...args) => {
186
- let str1 = '';
187
- args.forEach(
188
- (value)=>{
189
- let str2 = `${value.selectors.toString()} {\n`;
150
+ let cssOutput = '';
151
+
152
+ args.forEach((value) => {
153
+ if (value && value.selectors) {
154
+ let rule = `${value.selectors.join(', ')} {\n`;
155
+
156
+ // Add all properties (excluding 'selectors')
190
157
  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`;
158
+ if (key !== 'selectors' && value.hasOwnProperty(key)) {
159
+ const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
160
+ rule += ` ${kebabKey}: ${value[key]};\n`;
194
161
  }
195
162
  }
196
- str2 += `}\n\n`;
197
- str1 += str2;
163
+
164
+ rule += `}\n\n`;
165
+ cssOutput += rule;
198
166
  }
199
- );
200
- chain.cssOutput = str1.trim();
167
+ });
168
+
169
+ chain.cssOutput = cssOutput.trim();
170
+ return cssOutput.trim();
201
171
  };
202
172
 
203
173
  const compile = (obj) => {
@@ -206,48 +176,29 @@ const compile = (obj) => {
206
176
  for (const key in obj) {
207
177
  if (obj.hasOwnProperty(key)) {
208
178
  const element = obj[key];
209
- let selectors = element.selectors || []; // Provide default empty array if selectors is undefined
179
+ let selectors = element.selectors || [];
210
180
  let elementCSS = '';
181
+
211
182
  for (let prop in element) {
212
183
  if (element.hasOwnProperty(prop) && prop !== 'selectors') {
213
184
  // Convert camelCase to kebab-case
214
- const kebabKey = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
185
+ const kebabKey = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
215
186
  elementCSS += ` ${kebabKey}: ${element[prop]};\n`;
216
187
  }
217
188
  }
218
- selectors = selectors.join();
219
- cssString += `${selectors} {\n${elementCSS}}\n`;
189
+
190
+ cssString += `${selectors.join(', ')} {\n${elementCSS}}\n`;
220
191
  }
221
192
  }
222
193
 
223
- chain.cssOutput = cssString;
194
+ chain.cssOutput = cssString.trim();
224
195
  };
225
196
 
226
- const get = (filename) => {
227
- const fileExt = path.extname(filename).toLowerCase();
228
- if (fileExt !== '.jcss') {
229
- throw new Error(`Import error: ${filename} must have .jcss extension`);
230
- }
231
- // Try to resolve the path
232
- const resolvedPath = path.resolve(process.cwd(), filename);
233
-
234
- // 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+')');
239
- }
240
-
241
- return require(resolvedPath);
242
- };
243
-
244
- if (typeof global !== 'undefined') {
245
- global.chain = chain;
246
- }
247
-
248
197
  module.exports = {
249
198
  chain,
199
+ $,
250
200
  run,
251
201
  compile,
252
- get
202
+ createTokens: tokenModule.createTokens,
203
+ responsive: tokenModule.responsive
253
204
  };
package/types.d.ts ADDED
@@ -0,0 +1,148 @@
1
+ /// <reference types="react" />
2
+
3
+ declare module '@melcanz85/chaincss' {
4
+ // Style definition returned by .block()
5
+ export interface StyleDefinition {
6
+ selectors: string[];
7
+ [cssProperty: string]: any;
8
+ }
9
+
10
+ // Base interface for CSS properties (dynamic)
11
+ export interface CSSPropertyBuilder {
12
+ [key: string]: (value: string | number) => ChainBuilder;
13
+ }
14
+
15
+ // Special methods interface
16
+ export interface SpecialMethods {
17
+ block(...selectors: string[]): StyleDefinition;
18
+ token?(path: string): ChainBuilder;
19
+ }
20
+
21
+ // ChainBuilder is the intersection of both
22
+ export type ChainBuilder = CSSPropertyBuilder & SpecialMethods;
23
+
24
+ // The main $ function
25
+ export function $(): ChainBuilder;
26
+
27
+ // Run function for inline styles
28
+ export function run(...styles: StyleDefinition[]): string;
29
+
30
+ // Compile function for objects
31
+ export function compile(styles: Record<string, StyleDefinition>): void;
32
+
33
+ // Get function for importing (VM-safe version)
34
+ export function get(filename: string): any;
35
+
36
+ // Chain object (internal state)
37
+ export const chain: {
38
+ cssOutput: string;
39
+ catcher: any;
40
+ cachedValidProperties: string[];
41
+ };
42
+
43
+ // Processor function
44
+ export function processor(inputFile: string, outputFile: string): Promise<void>;
45
+
46
+ // Watch function
47
+ export function watch(inputFile: string, outputFile: string): void;
48
+
49
+ // Atomic optimizer configuration
50
+ export interface AtomicConfig {
51
+ enabled?: boolean;
52
+ threshold?: number;
53
+ naming?: 'hash' | 'readable' | 'short';
54
+ cache?: boolean;
55
+ cachePath?: string;
56
+ minify?: boolean;
57
+ }
58
+
59
+ // Prefixer configuration
60
+ export interface PrefixerConfig {
61
+ mode?: 'auto' | 'full';
62
+ browsers?: string[];
63
+ enabled?: boolean;
64
+ sourceMap?: boolean;
65
+ sourceMapInline?: boolean;
66
+ }
67
+
68
+ // ChainCSS configuration
69
+ export interface ChainCSSConfig {
70
+ atomic?: AtomicConfig;
71
+ prefixer?: PrefixerConfig;
72
+ sourceMaps?: boolean;
73
+ }
74
+
75
+ // Function to configure ChainCSS
76
+ export function configure(config: ChainCSSConfig): void;
77
+
78
+ // Atomic optimizer instance
79
+ export const atomicOptimizer: {
80
+ optimize(styles: Record<string, StyleDefinition>): string;
81
+ getStats(): {
82
+ totalStyles: number;
83
+ atomicStyles: number;
84
+ uniqueProperties: number;
85
+ savings?: string;
86
+ };
87
+ };
88
+
89
+ // Token system types
90
+ export interface Tokens {
91
+ colors: Record<string, string | Record<string, string>>;
92
+ spacing: Record<string, string>;
93
+ typography: {
94
+ fontFamily: Record<string, string>;
95
+ fontSize: Record<string, string>;
96
+ fontWeight: Record<string, string>;
97
+ lineHeight: Record<string, string>;
98
+ };
99
+ breakpoints: Record<string, string>;
100
+ zIndex: Record<string, string>;
101
+ shadows: Record<string, string>;
102
+ borderRadius: Record<string, string>;
103
+ }
104
+
105
+ export class DesignTokens {
106
+ constructor(tokens: Partial<Tokens>);
107
+ get(path: string, defaultValue?: string): string;
108
+ toCSSVariables(prefix?: string): string;
109
+ createTheme(name: string, overrides: Record<string, any>): DesignTokens;
110
+ }
111
+
112
+ export const tokens: DesignTokens;
113
+ export function createTokens(customTokens: Partial<Tokens>): DesignTokens;
114
+ export function responsive(values: Record<string, string> | string): string;
115
+
116
+ // React hooks types (add to your existing declare module)
117
+ export interface UseChainStylesOptions {
118
+ cache?: boolean;
119
+ namespace?: string;
120
+ watch?: boolean;
121
+ }
122
+
123
+ export function useChainStyles(
124
+ styles: Record<string, any> | (() => Record<string, any>),
125
+ options?: UseChainStylesOptions
126
+ ): Record<string, string>;
127
+
128
+ export function useDynamicChainStyles(
129
+ styleFactory: () => Record<string, any>,
130
+ deps?: any[],
131
+ options?: UseChainStylesOptions
132
+ ): Record<string, string>;
133
+
134
+ export function useThemeChainStyles(
135
+ styles: Record<string, any> | ((theme: any) => Record<string, any>),
136
+ theme: any,
137
+ options?: UseChainStylesOptions
138
+ ): Record<string, string>;
139
+
140
+ export const ChainCSSGlobal: React.FC<{ styles: Record<string, any> }>;
141
+
142
+ export function withChainStyles(
143
+ styles: Record<string, any> | ((props: any) => Record<string, any>),
144
+ options?: UseChainStylesOptions
145
+ ): <P extends object>(Component: React.ComponentType<P>) => React.FC<P & { chainStyles?: Record<string, string> }>;
146
+
147
+ export function cx(...classes: (string | undefined | null | false)[]): string;
148
+ }
@@ -1,22 +0,0 @@
1
- name: Publish Package
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
-
8
- permissions:
9
- id-token: write
10
- contents: read
11
-
12
- jobs:
13
- publish:
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v4
17
- - uses: actions/setup-node@v4
18
- with:
19
- node-version: '24'
20
- registry-url: 'https://registry.npmjs.org'
21
- - run: npm ci
22
- - run: npm publish
package/publish.sh DELETED
@@ -1,7 +0,0 @@
1
- #!/bin/bash
2
- npm version patch
3
- git push --tags
4
- git checkout main
5
- git merge master
6
- git push origin main
7
- git checkout master