chaincss 2.0.6 → 2.1.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/CHANGELOG.md +30 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +28 -0
- package/README.md +454 -231
- package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +1 -0
- package/demo/index.html +16 -0
- package/demo/package.json +20 -0
- package/demo/src/App.tsx +117 -0
- package/demo/src/chaincss-barrel.ts +9 -0
- package/demo/src/main.tsx +8 -0
- package/demo/src/styles.chain.ts +300 -0
- package/demo/vite.config.ts +46 -0
- package/dist/cli/commands/build.d.ts +0 -1
- package/dist/cli/commands/cache.d.ts +1 -0
- package/dist/cli/commands/init.d.ts +6 -3
- package/dist/cli/commands/timeline.d.ts +0 -1
- package/dist/cli/commands/watch.d.ts +0 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +3213 -5296
- package/dist/cli/types.d.ts +51 -20
- package/dist/cli/utils/config-loader.d.ts +0 -1
- package/dist/cli/utils/file-utils.d.ts +27 -3
- package/dist/cli/utils/logger.d.ts +0 -1
- package/dist/compiler/Chain.d.ts +215 -0
- package/dist/compiler/animations.d.ts +76 -0
- package/dist/compiler/atomic-optimizer.d.ts +47 -12
- package/dist/compiler/breakpoints.d.ts +46 -0
- package/dist/compiler/btt.d.ts +36 -60
- package/dist/compiler/cache-manager.d.ts +58 -4
- package/dist/compiler/commonProps.d.ts +0 -1
- package/dist/compiler/content-addressable-cache.d.ts +78 -0
- package/dist/compiler/helpers.d.ts +54 -0
- package/dist/compiler/index.d.ts +16 -9
- package/dist/compiler/index.js +4450 -4316
- package/dist/compiler/prefixer.d.ts +17 -1
- package/dist/compiler/shorthands.d.ts +28 -0
- package/dist/compiler/suggestions.d.ts +43 -0
- package/dist/compiler/theme-contract.d.ts +16 -27
- package/dist/compiler/token-resolver.d.ts +69 -0
- package/dist/compiler/tokens.d.ts +33 -8
- package/dist/core/auto-detector.d.ts +34 -0
- package/dist/core/common-utils.d.ts +97 -0
- package/dist/core/compiler.d.ts +63 -23
- package/dist/core/constants.d.ts +137 -36
- package/dist/core/smart-chain.d.ts +3 -0
- package/dist/core/types.d.ts +122 -15
- package/dist/core/utils.d.ts +134 -17
- package/dist/index.d.ts +52 -8
- package/dist/index.js +7090 -5578
- package/dist/plugins/vite.d.ts +7 -5
- package/dist/plugins/vite.js +2964 -25641
- package/dist/plugins/webpack.d.ts +24 -1
- package/dist/plugins/webpack.js +209 -72
- package/dist/runtime/Chain.d.ts +32 -0
- package/dist/runtime/auto-hooks.d.ts +11 -0
- package/dist/runtime/hmr.d.ts +22 -2
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +3649 -301
- package/dist/runtime/injector.d.ts +39 -71
- package/dist/runtime/react.d.ts +17 -12
- package/dist/runtime/svelte.d.ts +15 -0
- package/dist/runtime/types.d.ts +126 -4
- package/dist/runtime/utils.d.ts +0 -1
- package/dist/runtime/vue.d.ts +34 -14
- package/package.json +59 -66
- package/src/cli/commands/build.ts +133 -0
- package/src/cli/commands/cache.ts +371 -0
- package/src/cli/commands/init.ts +230 -0
- package/src/cli/commands/timeline.ts +435 -0
- package/src/cli/commands/watch.ts +211 -0
- package/src/cli/index.ts +226 -0
- package/src/cli/types.ts +100 -0
- package/src/cli/utils/config-loader.ts +174 -0
- package/src/cli/utils/file-utils.ts +139 -0
- package/src/cli/utils/logger.ts +74 -0
- package/src/compiler/Chain.ts +831 -0
- package/src/compiler/animations.ts +517 -0
- package/src/compiler/atomic-optimizer.ts +786 -0
- package/src/compiler/breakpoints.ts +347 -0
- package/src/compiler/btt.ts +1147 -0
- package/src/compiler/cache-manager.ts +446 -0
- package/src/compiler/commonProps.ts +18 -0
- package/src/compiler/content-addressable-cache.ts +478 -0
- package/src/compiler/helpers.ts +407 -0
- package/src/compiler/index.ts +72 -0
- package/src/compiler/prefixer.ts +724 -0
- package/src/compiler/shorthands.ts +558 -0
- package/src/compiler/suggestions.ts +436 -0
- package/src/compiler/theme-contract.ts +197 -0
- package/src/compiler/token-resolver.ts +241 -0
- package/src/compiler/tokens.ts +612 -0
- package/src/core/auto-detector.ts +187 -0
- package/src/core/common-utils.ts +423 -0
- package/src/core/compiler.ts +835 -0
- package/src/core/constants.ts +424 -0
- package/src/core/index.ts +107 -0
- package/src/core/smart-chain.ts +163 -0
- package/src/core/types.ts +257 -0
- package/src/core/utils.ts +598 -0
- package/src/index.ts +208 -0
- package/src/plugins/vite.d.ts +316 -0
- package/src/plugins/vite.ts +424 -0
- package/src/plugins/webpack.d.ts +289 -0
- package/src/plugins/webpack.ts +416 -0
- package/src/runtime/Chain.ts +242 -0
- package/src/runtime/auto-hooks.tsx +127 -0
- package/src/runtime/auto-vue.ts +72 -0
- package/src/runtime/hmr.ts +212 -0
- package/src/runtime/index.ts +82 -0
- package/src/runtime/injector.ts +273 -0
- package/src/runtime/react.tsx +269 -0
- package/src/runtime/svelte.ts +15 -0
- package/src/runtime/types.ts +256 -0
- package/src/runtime/utils.ts +128 -0
- package/src/runtime/vite-env.d.ts +120 -0
- package/src/runtime/vue.ts +231 -0
- package/tsconfig.build.json +41 -0
- package/tsconfig.json +25 -0
- package/tsconfig.runtimes.json +18 -0
- package/dist/cli/cli.cjs +0 -7
- package/dist/cli/commands/build.d.ts.map +0 -1
- package/dist/cli/commands/compile.d.ts +0 -3
- package/dist/cli/commands/compile.d.ts.map +0 -1
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/timeline.d.ts.map +0 -1
- package/dist/cli/commands/watch.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/utils/config-loader.d.ts.map +0 -1
- package/dist/cli/utils/file-utils.d.ts.map +0 -1
- package/dist/cli/utils/logger.d.ts.map +0 -1
- package/dist/compiler/atomic-optimizer.d.ts.map +0 -1
- package/dist/compiler/btt.d.ts.map +0 -1
- package/dist/compiler/cache-manager.d.ts.map +0 -1
- package/dist/compiler/commonProps.d.ts.map +0 -1
- package/dist/compiler/index.d.ts.map +0 -1
- package/dist/compiler/prefixer.d.ts.map +0 -1
- package/dist/compiler/theme-contract.d.ts.map +0 -1
- package/dist/compiler/tokens.d.ts.map +0 -1
- package/dist/compiler/types.d.ts +0 -57
- package/dist/compiler/types.d.ts.map +0 -1
- package/dist/core/compiler.d.ts.map +0 -1
- package/dist/core/constants.d.ts.map +0 -1
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/utils.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/plugins/vite.d.ts.map +0 -1
- package/dist/plugins/webpack.d.ts.map +0 -1
- package/dist/runtime/hmr.d.ts.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/injector.d.ts.map +0 -1
- package/dist/runtime/react.d.ts.map +0 -1
- package/dist/runtime/react.js +0 -270
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/utils.d.ts.map +0 -1
- package/dist/runtime/vue.d.ts.map +0 -1
- package/dist/runtime/vue.js +0 -232
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// src/core/auto-detector.ts
|
|
2
|
+
|
|
3
|
+
export type ValueType = 'static' | 'dynamic' | 'runtime-only';
|
|
4
|
+
export type Mode = 'build' | 'runtime' | 'hybrid' | 'auto';
|
|
5
|
+
|
|
6
|
+
export interface DetectedPart {
|
|
7
|
+
type: ValueType;
|
|
8
|
+
prop: string;
|
|
9
|
+
value: any;
|
|
10
|
+
originalValue: any;
|
|
11
|
+
index: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AnalysisResult {
|
|
15
|
+
staticParts: DetectedPart[];
|
|
16
|
+
dynamicParts: DetectedPart[];
|
|
17
|
+
runtimeOnlyParts: DetectedPart[];
|
|
18
|
+
isHybrid: boolean;
|
|
19
|
+
mode: Mode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class AutoDetector {
|
|
23
|
+
private static instance: AutoDetector;
|
|
24
|
+
private dynamicPatterns: RegExp[] = [
|
|
25
|
+
/\$\{.*\}/, // Template literals: ${variable}
|
|
26
|
+
/props\.[a-zA-Z]+/, // Props access: props.color
|
|
27
|
+
/theme\.[a-zA-Z]+/, // Theme access: theme.primary
|
|
28
|
+
/state\.[a-zA-Z]+/, // State access: state.isActive
|
|
29
|
+
/this\.[a-zA-Z]+/, // This binding
|
|
30
|
+
/useContext\(/, // React hook
|
|
31
|
+
/useSelector\(/, // Redux selector
|
|
32
|
+
/getState\(/, // Store getter
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
private staticPatterns: RegExp[] = [
|
|
36
|
+
/^#[0-9a-f]{3,6}$/i, // Hex colors
|
|
37
|
+
/^[a-z]+$/, // Simple words (red, blue, flex)
|
|
38
|
+
/^\d+(?:\.\d+)?(?:px|rem|em|%)?$/, // Numbers with optional units
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
private debug = false;
|
|
42
|
+
|
|
43
|
+
static getInstance(): AutoDetector {
|
|
44
|
+
if (!AutoDetector.instance) {
|
|
45
|
+
AutoDetector.instance = new AutoDetector();
|
|
46
|
+
}
|
|
47
|
+
return AutoDetector.instance;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
enableDebug(enabled: boolean): void {
|
|
51
|
+
this.debug = enabled;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
detectValueType(value: any, prop?: string): ValueType {
|
|
55
|
+
// Runtime-only patterns (functions, callbacks)
|
|
56
|
+
if (typeof value === 'function') {
|
|
57
|
+
if (this.debug) console.log(`[AutoDetector] Function detected for ${prop} -> runtime-only`);
|
|
58
|
+
return 'runtime-only';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Undefined or null values
|
|
62
|
+
if (value === undefined || value === null) {
|
|
63
|
+
return 'static';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Objects (nested styles)
|
|
67
|
+
if (typeof value === 'object' && value !== null) {
|
|
68
|
+
if (this.debug) console.log(`[AutoDetector] Object detected for ${prop} -> dynamic`);
|
|
69
|
+
return 'dynamic';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check static patterns
|
|
73
|
+
if (typeof value === 'string') {
|
|
74
|
+
// Check if it matches static patterns
|
|
75
|
+
for (const pattern of this.staticPatterns) {
|
|
76
|
+
if (pattern.test(value)) {
|
|
77
|
+
if (this.debug) console.log(`[AutoDetector] Static pattern matched for ${prop}: ${value}`);
|
|
78
|
+
return 'static';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check dynamic patterns
|
|
83
|
+
for (const pattern of this.dynamicPatterns) {
|
|
84
|
+
if (pattern.test(value)) {
|
|
85
|
+
if (this.debug) console.log(`[AutoDetector] Dynamic pattern matched for ${prop}: ${value}`);
|
|
86
|
+
return 'dynamic';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Numbers, booleans are static
|
|
92
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
93
|
+
return 'static';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Default to static
|
|
97
|
+
return 'static';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
analyzeChain(calls: Array<{ prop: string; value: any; index: number }>): AnalysisResult {
|
|
101
|
+
const staticParts: DetectedPart[] = [];
|
|
102
|
+
const dynamicParts: DetectedPart[] = [];
|
|
103
|
+
const runtimeOnlyParts: DetectedPart[] = [];
|
|
104
|
+
|
|
105
|
+
for (const call of calls) {
|
|
106
|
+
const type = this.detectValueType(call.value, call.prop);
|
|
107
|
+
const part: DetectedPart = {
|
|
108
|
+
type,
|
|
109
|
+
prop: call.prop,
|
|
110
|
+
value: call.value,
|
|
111
|
+
originalValue: call.value,
|
|
112
|
+
index: call.index
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
switch (type) {
|
|
116
|
+
case 'static':
|
|
117
|
+
staticParts.push(part);
|
|
118
|
+
break;
|
|
119
|
+
case 'dynamic':
|
|
120
|
+
dynamicParts.push(part);
|
|
121
|
+
break;
|
|
122
|
+
case 'runtime-only':
|
|
123
|
+
runtimeOnlyParts.push(part);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const isHybrid = staticParts.length > 0 && (dynamicParts.length > 0 || runtimeOnlyParts.length > 0);
|
|
129
|
+
let mode: Mode = 'build';
|
|
130
|
+
|
|
131
|
+
if (isHybrid) {
|
|
132
|
+
mode = 'hybrid';
|
|
133
|
+
} else if (dynamicParts.length > 0 || runtimeOnlyParts.length > 0) {
|
|
134
|
+
mode = 'runtime';
|
|
135
|
+
} else {
|
|
136
|
+
mode = 'build';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (this.debug) {
|
|
140
|
+
console.log('[AutoDetector] Analysis:', {
|
|
141
|
+
static: staticParts.length,
|
|
142
|
+
dynamic: dynamicParts.length,
|
|
143
|
+
runtimeOnly: runtimeOnlyParts.length,
|
|
144
|
+
mode,
|
|
145
|
+
isHybrid
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
staticParts,
|
|
151
|
+
dynamicParts,
|
|
152
|
+
runtimeOnlyParts,
|
|
153
|
+
isHybrid,
|
|
154
|
+
mode
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
addDynamicPattern(pattern: RegExp): void {
|
|
159
|
+
this.dynamicPatterns.push(pattern);
|
|
160
|
+
if (this.debug) console.log(`[AutoDetector] Added dynamic pattern: ${pattern}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
addStaticPattern(pattern: RegExp): void {
|
|
164
|
+
this.staticPatterns.push(pattern);
|
|
165
|
+
if (this.debug) console.log(`[AutoDetector] Added static pattern: ${pattern}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
reset(): void {
|
|
169
|
+
this.dynamicPatterns = [
|
|
170
|
+
/\$\{.*\}/,
|
|
171
|
+
/props\.[a-zA-Z]+/,
|
|
172
|
+
/theme\.[a-zA-Z]+/,
|
|
173
|
+
/state\.[a-zA-Z]+/,
|
|
174
|
+
/this\.[a-zA-Z]+/,
|
|
175
|
+
/useContext\(/,
|
|
176
|
+
/useSelector\(/,
|
|
177
|
+
/getState\(/,
|
|
178
|
+
];
|
|
179
|
+
this.staticPatterns = [
|
|
180
|
+
/^#[0-9a-f]{3,6}$/i,
|
|
181
|
+
/^[a-z]+$/,
|
|
182
|
+
/^\d+(?:\.\d+)?(?:px|rem|em|%)?$/,
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const autoDetector = AutoDetector.getInstance();
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// src/core/common-utils.ts
|
|
2
|
+
|
|
3
|
+
import { shorthandMap, macros } from '../compiler/shorthands.js';
|
|
4
|
+
import type { DesignTokens } from '../compiler/tokens.js';
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Utility Functions
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert camelCase to kebab-case
|
|
12
|
+
*/
|
|
13
|
+
export function kebabCase(str: string): string {
|
|
14
|
+
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert kebab-case to camelCase
|
|
19
|
+
*/
|
|
20
|
+
export function camelCase(str: string): string {
|
|
21
|
+
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Token Resolution
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve token references in a value
|
|
30
|
+
* Supports $token.path format
|
|
31
|
+
*/
|
|
32
|
+
export function resolveToken(
|
|
33
|
+
value: any,
|
|
34
|
+
tokenStore: Record<string, any> | DesignTokens = {},
|
|
35
|
+
debug: boolean = false
|
|
36
|
+
): any {
|
|
37
|
+
if (typeof value !== 'string' || !value.includes('$')) return value;
|
|
38
|
+
|
|
39
|
+
return value.replace(/\$([a-zA-Z0-9.-]+)/g, (match, pathStr) => {
|
|
40
|
+
const parts = pathStr.split('.');
|
|
41
|
+
let current: any = tokenStore;
|
|
42
|
+
|
|
43
|
+
// Handle DesignTokens instance
|
|
44
|
+
if (current && typeof current.get === 'function') {
|
|
45
|
+
const resolved = current.get(pathStr);
|
|
46
|
+
if (resolved !== undefined && resolved !== null) {
|
|
47
|
+
if (debug) {
|
|
48
|
+
console.log(`✨ Resolved ${match} to ${resolved}`);
|
|
49
|
+
}
|
|
50
|
+
return String(resolved);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle plain object
|
|
55
|
+
for (const part of parts) {
|
|
56
|
+
if (current && current[part] !== undefined) {
|
|
57
|
+
current = current[part];
|
|
58
|
+
} else {
|
|
59
|
+
if (debug) {
|
|
60
|
+
console.warn(`⚠️ Token not found: ${match}`);
|
|
61
|
+
}
|
|
62
|
+
return match;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof current === 'string' || typeof current === 'number') {
|
|
67
|
+
if (debug) {
|
|
68
|
+
console.log(`✨ Resolved ${match} to ${current}`);
|
|
69
|
+
}
|
|
70
|
+
return String(current);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return match;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// Style Object Processing
|
|
79
|
+
// ============================================================================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Process a style object, expanding shorthands and resolving tokens
|
|
83
|
+
*/
|
|
84
|
+
export function processStyleObject(
|
|
85
|
+
obj: Record<string, any>,
|
|
86
|
+
tokenStore: Record<string, any> | DesignTokens = {},
|
|
87
|
+
options: { useTokens?: boolean; debug?: boolean } = {}
|
|
88
|
+
): string {
|
|
89
|
+
const { useTokens = true, debug = false } = options;
|
|
90
|
+
let css = '';
|
|
91
|
+
const expandedProps: Record<string, any> = {};
|
|
92
|
+
|
|
93
|
+
if (debug) {
|
|
94
|
+
console.log('[ChainCSS] Processing style object:', obj);
|
|
95
|
+
if (tokenStore && typeof tokenStore === 'object') {
|
|
96
|
+
const tokenKeys = Object.keys(tokenStore);
|
|
97
|
+
if (tokenKeys.length > 0) {
|
|
98
|
+
console.log('[ChainCSS] Token store available:', tokenKeys);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (let [key, value] of Object.entries(obj)) {
|
|
104
|
+
// Skip internal properties
|
|
105
|
+
if (key.startsWith('_')) continue;
|
|
106
|
+
|
|
107
|
+
// Skip nested objects (handled separately)
|
|
108
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handle macros (mx, my, px, py, etc.)
|
|
113
|
+
if (macros && macros[key]) {
|
|
114
|
+
try {
|
|
115
|
+
macros[key](value, expandedProps, useTokens);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.warn(`[ChainCSS] Error applying macro "${key}":`, error);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
const realKey = shorthandMap[key] || key;
|
|
121
|
+
expandedProps[realKey] = value;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (debug) {
|
|
126
|
+
console.log('[ChainCSS] Expanded properties:', expandedProps);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Generate CSS string from expanded properties
|
|
130
|
+
const unitlessProps = [
|
|
131
|
+
'opacity', 'zIndex', 'fontWeight', 'flex', 'flexGrow', 'flexShrink',
|
|
132
|
+
'order', 'gridColumn', 'gridRow', 'animationIterationCount', 'lineHeight'
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
for (let [key, value] of Object.entries(expandedProps)) {
|
|
136
|
+
if (debug) {
|
|
137
|
+
console.log(`[ChainCSS] Processing property: ${key} = ${value}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Resolve token references
|
|
141
|
+
let finalValue = value;
|
|
142
|
+
if (useTokens && typeof value === 'string') {
|
|
143
|
+
finalValue = resolveToken(value, tokenStore, debug);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const kebabKey = kebabCase(key);
|
|
147
|
+
|
|
148
|
+
// Add unit for numeric values
|
|
149
|
+
let unit = '';
|
|
150
|
+
if (typeof value === 'number' && !unitlessProps.includes(key)) {
|
|
151
|
+
unit = 'px';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
css += ` ${kebabKey}: ${finalValue}${unit};\n`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return css;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Style Extraction
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Extract CSS string from style definition
|
|
166
|
+
*/
|
|
167
|
+
export function extractCSS(styleDef: Record<string, any>): string {
|
|
168
|
+
let css = '';
|
|
169
|
+
const selectors = styleDef.selectors || [''];
|
|
170
|
+
|
|
171
|
+
for (const [key, value] of Object.entries(styleDef)) {
|
|
172
|
+
if (key === 'selectors' || key === 'hover' || key === 'atRules' || key === 'nestedRules') {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const kebabKey = kebabCase(key);
|
|
177
|
+
css += `${kebabKey}: ${value};`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!css) return '';
|
|
181
|
+
|
|
182
|
+
return `${selectors.join(', ')} { ${css} }`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Extract hover CSS from style definition
|
|
187
|
+
*/
|
|
188
|
+
export function extractHoverCSS(styleDef: Record<string, any>): string {
|
|
189
|
+
const hover = styleDef.hover;
|
|
190
|
+
if (!hover || typeof hover !== 'object') return '';
|
|
191
|
+
|
|
192
|
+
const selectors = styleDef.selectors || [''];
|
|
193
|
+
let hoverCSS = '';
|
|
194
|
+
|
|
195
|
+
for (const [key, value] of Object.entries(hover)) {
|
|
196
|
+
const kebabKey = kebabCase(key);
|
|
197
|
+
hoverCSS += `${kebabKey}: ${value};`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!hoverCSS) return '';
|
|
201
|
+
|
|
202
|
+
return `${selectors.join(', ')}:hover { ${hoverCSS} }`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Style Merging
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Merge multiple style objects
|
|
211
|
+
*/
|
|
212
|
+
export function mergeStyles(...styles: Record<string, any>[]): Record<string, any> {
|
|
213
|
+
const result: Record<string, any> = {};
|
|
214
|
+
|
|
215
|
+
for (const style of styles) {
|
|
216
|
+
if (!style) continue;
|
|
217
|
+
|
|
218
|
+
for (const [key, value] of Object.entries(style)) {
|
|
219
|
+
if (key === 'hover' && result.hover && typeof value === 'object') {
|
|
220
|
+
result.hover = { ...result.hover, ...value };
|
|
221
|
+
} else if (key === 'selectors' && result.selectors) {
|
|
222
|
+
const newSelectors = Array.isArray(value) ? value : [value];
|
|
223
|
+
const existingSelectors = Array.isArray(result.selectors) ? result.selectors : [result.selectors];
|
|
224
|
+
result.selectors = [...new Set([...existingSelectors, ...newSelectors])];
|
|
225
|
+
} else {
|
|
226
|
+
result[key] = value;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ============================================================================
|
|
235
|
+
// Validation Utilities
|
|
236
|
+
// ============================================================================
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check if a value is a valid CSS length
|
|
240
|
+
*/
|
|
241
|
+
export function isValidCSSLength(value: any): boolean {
|
|
242
|
+
if (typeof value === 'number') return true;
|
|
243
|
+
if (typeof value !== 'string') return false;
|
|
244
|
+
|
|
245
|
+
const lengthRegex = /^[+-]?\d*\.?\d+(px|rem|em|%|vw|vh|vmin|vmax|ch|ex|cm|mm|in|pt|pc)?$/;
|
|
246
|
+
return lengthRegex.test(value);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if a value is a valid CSS color
|
|
251
|
+
*/
|
|
252
|
+
export function isValidCSSColor(value: any): boolean {
|
|
253
|
+
if (typeof value !== 'string') return false;
|
|
254
|
+
|
|
255
|
+
// Hex colors
|
|
256
|
+
if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value)) return true;
|
|
257
|
+
|
|
258
|
+
// RGB/RGBA
|
|
259
|
+
if (/^rgba?\([^)]+\)$/.test(value)) return true;
|
|
260
|
+
|
|
261
|
+
// HSL/HSLA
|
|
262
|
+
if (/^hsla?\([^)]+\)$/.test(value)) return true;
|
|
263
|
+
|
|
264
|
+
// Named colors
|
|
265
|
+
const namedColors = [
|
|
266
|
+
'black', 'white', 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta',
|
|
267
|
+
'gray', 'grey', 'transparent', 'currentColor', 'inherit', 'initial',
|
|
268
|
+
'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige',
|
|
269
|
+
'bisque', 'blanchedalmond', 'blueviolet', 'brown', 'burlywood', 'cadetblue',
|
|
270
|
+
'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson'
|
|
271
|
+
];
|
|
272
|
+
if (namedColors.includes(value.toLowerCase())) return true;
|
|
273
|
+
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// Selector Utilities
|
|
279
|
+
// ============================================================================
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Escape CSS selector
|
|
283
|
+
*/
|
|
284
|
+
export function escapeSelector(selector: string): string {
|
|
285
|
+
if (!selector) return '';
|
|
286
|
+
|
|
287
|
+
// Escape special characters in CSS selectors
|
|
288
|
+
return selector.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Clean class name for CSS
|
|
293
|
+
*/
|
|
294
|
+
export function cleanClassName(className: string): string {
|
|
295
|
+
if (!className) return '';
|
|
296
|
+
|
|
297
|
+
// Remove invalid characters and ensure it starts with a letter or underscore
|
|
298
|
+
let cleaned = className.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
299
|
+
|
|
300
|
+
// Ensure it starts with a valid character
|
|
301
|
+
if (!/^[a-zA-Z_]/.test(cleaned)) {
|
|
302
|
+
cleaned = `c-${cleaned}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return cleaned;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ============================================================================
|
|
309
|
+
// Value Extraction
|
|
310
|
+
// ============================================================================
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Extract numeric value from CSS value
|
|
314
|
+
*/
|
|
315
|
+
export function extractNumericValue(value: string): number {
|
|
316
|
+
const match = value.match(/^[+-]?\d*\.?\d+/);
|
|
317
|
+
return match ? parseFloat(match[0]) : 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Extract unit from CSS value
|
|
322
|
+
*/
|
|
323
|
+
export function extractUnit(value: string): string {
|
|
324
|
+
const match = value.match(/[a-z%]+$/);
|
|
325
|
+
return match ? match[0] : '';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Add unit to numeric value if missing
|
|
330
|
+
*/
|
|
331
|
+
export function addUnit(value: number | string, unit: string = 'px'): string {
|
|
332
|
+
if (typeof value === 'number') return `${value}${unit}`;
|
|
333
|
+
if (typeof value === 'string') {
|
|
334
|
+
if (/^\d+(?:\.\d+)?$/.test(value)) return `${value}${unit}`;
|
|
335
|
+
return value;
|
|
336
|
+
}
|
|
337
|
+
return String(value);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// Class Name Utilities
|
|
342
|
+
// ============================================================================
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Sort class names for consistent output
|
|
346
|
+
*/
|
|
347
|
+
export function sortClassNames(classNames: string[]): string[] {
|
|
348
|
+
return [...classNames].sort((a, b) => {
|
|
349
|
+
// Atomic classes (a-*) come first
|
|
350
|
+
const aIsAtomic = a.startsWith('a-');
|
|
351
|
+
const bIsAtomic = b.startsWith('a-');
|
|
352
|
+
if (aIsAtomic && !bIsAtomic) return -1;
|
|
353
|
+
if (!aIsAtomic && bIsAtomic) return 1;
|
|
354
|
+
|
|
355
|
+
// Component classes (c-*) come next
|
|
356
|
+
const aIsComponent = a.startsWith('c-');
|
|
357
|
+
const bIsComponent = b.startsWith('c-');
|
|
358
|
+
if (aIsComponent && !bIsComponent) return -1;
|
|
359
|
+
if (!aIsComponent && bIsComponent) return 1;
|
|
360
|
+
|
|
361
|
+
// Then alphabetically
|
|
362
|
+
return a.localeCompare(b);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Join class names safely
|
|
368
|
+
*/
|
|
369
|
+
export function cn(...classes: (string | undefined | null | false)[]): string {
|
|
370
|
+
return classes.filter(Boolean).join(' ');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ============================================================================
|
|
374
|
+
// Debug Utilities
|
|
375
|
+
// ============================================================================
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Create a debug logger
|
|
379
|
+
*/
|
|
380
|
+
let debugMode = false;
|
|
381
|
+
|
|
382
|
+
export function enableDebug(enable: boolean = true): void {
|
|
383
|
+
debugMode = enable;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function isDebugEnabled(): boolean {
|
|
387
|
+
return debugMode;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Debug log function
|
|
392
|
+
*/
|
|
393
|
+
export function debugLog(message: string, ...args: any[]): void {
|
|
394
|
+
if (debugMode) {
|
|
395
|
+
console.log(`[ChainCSS Debug] ${message}`, ...args);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Default Export
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
export default {
|
|
404
|
+
kebabCase,
|
|
405
|
+
camelCase,
|
|
406
|
+
resolveToken,
|
|
407
|
+
processStyleObject,
|
|
408
|
+
extractCSS,
|
|
409
|
+
extractHoverCSS,
|
|
410
|
+
mergeStyles,
|
|
411
|
+
isValidCSSLength,
|
|
412
|
+
isValidCSSColor,
|
|
413
|
+
escapeSelector,
|
|
414
|
+
cleanClassName,
|
|
415
|
+
extractNumericValue,
|
|
416
|
+
extractUnit,
|
|
417
|
+
addUnit,
|
|
418
|
+
sortClassNames,
|
|
419
|
+
cn,
|
|
420
|
+
enableDebug,
|
|
421
|
+
isDebugEnabled,
|
|
422
|
+
debugLog
|
|
423
|
+
};
|