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,436 @@
|
|
|
1
|
+
// src/compiler/suggestions.ts
|
|
2
|
+
// Types
|
|
3
|
+
export interface SuggestionMatch {
|
|
4
|
+
name: string;
|
|
5
|
+
distance: number;
|
|
6
|
+
type: 'shorthand' | 'css-property' | 'macro' | 'animation' | 'breakpoint';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Known shorthands (from shorthandMap)
|
|
10
|
+
export const KNOWN_SHORTHANDS: string[] = [
|
|
11
|
+
// Spacing
|
|
12
|
+
'm', 'mt', 'mr', 'mb', 'ml',
|
|
13
|
+
'p', 'pt', 'pr', 'pb', 'pl',
|
|
14
|
+
'mx', 'my', 'px', 'py',
|
|
15
|
+
'inset', 'insetX', 'insetY',
|
|
16
|
+
|
|
17
|
+
// Sizing
|
|
18
|
+
'w', 'h', 'minW', 'maxW', 'minH', 'maxH',
|
|
19
|
+
'size', 'aspect',
|
|
20
|
+
|
|
21
|
+
// Display & Layout
|
|
22
|
+
'd', 'pos', 'flex', 'grid', 'inlineFlex', 'inlineGrid',
|
|
23
|
+
'flexDir', 'flexWrap', 'justify', 'items', 'align', 'content', 'self',
|
|
24
|
+
'center', 'flexCenter', 'gridCenter', 'stack', 'gridTable',
|
|
25
|
+
'cols', 'rows', 'gap', 'gapX', 'gapY', 'grow', 'shrink', 'basis', 'order',
|
|
26
|
+
|
|
27
|
+
// Colors & Backgrounds
|
|
28
|
+
'bg', 'c', 'text', 'op',
|
|
29
|
+
|
|
30
|
+
// Borders
|
|
31
|
+
'border', 'borderW', 'borderC', 'borderS',
|
|
32
|
+
'borderT', 'borderR', 'borderB', 'borderL',
|
|
33
|
+
'borderX', 'borderY',
|
|
34
|
+
'rounded', 'br', 'radius',
|
|
35
|
+
'roundedTL', 'roundedTR', 'roundedBR', 'roundedBL',
|
|
36
|
+
|
|
37
|
+
// Typography
|
|
38
|
+
'fontF', 'fs', 'fw', 'lh', 'ls', 'align',
|
|
39
|
+
|
|
40
|
+
// Effects
|
|
41
|
+
'shadow', 'truncate', 'hide', 'show', 'unselectable',
|
|
42
|
+
'scrollable', 'glass', 'glow', 'textGradient', 'meshGradient', 'noise',
|
|
43
|
+
|
|
44
|
+
// Positioning
|
|
45
|
+
'absolute', 'fixed', 'sticky', 'relative',
|
|
46
|
+
|
|
47
|
+
// Utilities
|
|
48
|
+
'pill', 'container', 'fullScreen', 'shimmer', 'bento',
|
|
49
|
+
'pressable', 'focusRing', 'outlineDebug', 'skeleton',
|
|
50
|
+
'safeArea', 'clickScale', 'onInteracting', 'children',
|
|
51
|
+
'dark', 'light', 'fluidText'
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Common CSS properties for suggestions
|
|
55
|
+
export const COMMON_CSS_PROPERTIES: string[] = [
|
|
56
|
+
'display', 'position', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
|
57
|
+
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
|
58
|
+
'color', 'background', 'background-color', 'background-image', 'background-size', 'background-position',
|
|
59
|
+
'border', 'border-width', 'border-style', 'border-color', 'border-radius',
|
|
60
|
+
'width', 'height', 'max-width', 'max-height', 'min-width', 'min-height',
|
|
61
|
+
'font-size', 'font-weight', 'font-family', 'line-height', 'letter-spacing', 'text-align',
|
|
62
|
+
'cursor', 'opacity', 'z-index', 'overflow', 'overflow-x', 'overflow-y',
|
|
63
|
+
'flex', 'flex-direction', 'flex-wrap', 'justify-content', 'align-items', 'align-self', 'gap',
|
|
64
|
+
'grid', 'grid-template-columns', 'grid-template-rows', 'grid-column', 'grid-row',
|
|
65
|
+
'transition', 'transform', 'animation', 'box-shadow', 'text-shadow',
|
|
66
|
+
'filter', 'backdrop-filter', 'clip-path', 'mask',
|
|
67
|
+
'pointer-events', 'user-select', 'resize', 'appearance'
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Animation presets (from animations.ts)
|
|
71
|
+
export const ANIMATION_PRESETS: string[] = [
|
|
72
|
+
'fadeIn', 'fadeOut', 'fadeInUp', 'fadeInDown', 'fadeInLeft', 'fadeInRight',
|
|
73
|
+
'fadeOutUp', 'fadeOutDown', 'slideInUp', 'slideInDown', 'slideInLeft', 'slideInRight',
|
|
74
|
+
'slideOutUp', 'slideOutDown', 'zoomIn', 'zoomOut', 'zoomInUp', 'zoomInDown',
|
|
75
|
+
'bounce', 'bounceIn', 'bounceOut', 'pulse', 'pulseGlow', 'shake', 'shakeX', 'shakeY',
|
|
76
|
+
'spin', 'spinReverse', 'wiggle', 'wobble', 'flip', 'flipX', 'blink', 'typing',
|
|
77
|
+
'cursor', 'shimmer', 'ripple', 'float', 'sink', 'swing', 'flash',
|
|
78
|
+
'textReveal', 'textGlitch'
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
// Breakpoint names (from breakpoints.ts)
|
|
82
|
+
export const BREAKPOINTS: string[] = [
|
|
83
|
+
'sm', 'md', 'lg', 'xl', '2xl',
|
|
84
|
+
'mobile', 'tablet', 'desktop',
|
|
85
|
+
'mobile-sm', 'mobile-md', 'tablet-sm', 'tablet-lg',
|
|
86
|
+
'desktop-sm', 'desktop-md', 'desktop-lg',
|
|
87
|
+
'portrait', 'landscape',
|
|
88
|
+
'dark', 'light', 'reducedMotion', 'highContrast',
|
|
89
|
+
'print', 'hover', 'no-hover', 'fine', 'coarse'
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
// Simple Levenshtein distance for finding closest matches
|
|
93
|
+
function levenshteinDistance(a: string, b: string): number {
|
|
94
|
+
if (a.length === 0) return b.length;
|
|
95
|
+
if (b.length === 0) return a.length;
|
|
96
|
+
|
|
97
|
+
const matrix: number[][] = [];
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i <= b.length; i++) {
|
|
100
|
+
matrix[i] = [i];
|
|
101
|
+
}
|
|
102
|
+
for (let j = 0; j <= a.length; j++) {
|
|
103
|
+
matrix[0][j] = j;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (let i = 1; i <= b.length; i++) {
|
|
107
|
+
for (let j = 1; j <= a.length; j++) {
|
|
108
|
+
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
|
|
109
|
+
matrix[i][j] = Math.min(
|
|
110
|
+
matrix[i - 1][j] + 1, // deletion
|
|
111
|
+
matrix[i][j - 1] + 1, // insertion
|
|
112
|
+
matrix[i - 1][j - 1] + cost // substitution
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return matrix[b.length][a.length];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Calculate similarity score (0-1, higher is better)
|
|
121
|
+
function similarityScore(a: string, b: string): number {
|
|
122
|
+
const distance = levenshteinDistance(a.toLowerCase(), b.toLowerCase());
|
|
123
|
+
const maxLen = Math.max(a.length, b.length);
|
|
124
|
+
return maxLen === 0 ? 1 : 1 - (distance / maxLen);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Find best matches with scores
|
|
128
|
+
function findBestMatches(
|
|
129
|
+
query: string,
|
|
130
|
+
candidates: string[],
|
|
131
|
+
maxResults: number = 3,
|
|
132
|
+
maxDistance: number = 3
|
|
133
|
+
): SuggestionMatch[] {
|
|
134
|
+
const matches: SuggestionMatch[] = [];
|
|
135
|
+
|
|
136
|
+
for (const candidate of candidates) {
|
|
137
|
+
const distance = levenshteinDistance(query.toLowerCase(), candidate.toLowerCase());
|
|
138
|
+
if (distance <= maxDistance) {
|
|
139
|
+
matches.push({
|
|
140
|
+
name: candidate,
|
|
141
|
+
distance,
|
|
142
|
+
type: getTypeForCandidate(candidate)
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Sort by distance (lower is better) then by name
|
|
148
|
+
matches.sort((a, b) => {
|
|
149
|
+
if (a.distance !== b.distance) return a.distance - b.distance;
|
|
150
|
+
return a.name.localeCompare(b.name);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return matches.slice(0, maxResults);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Get type for a candidate
|
|
157
|
+
function getTypeForCandidate(candidate: string): 'shorthand' | 'css-property' | 'macro' | 'animation' | 'breakpoint' {
|
|
158
|
+
if (KNOWN_SHORTHANDS.includes(candidate)) return 'shorthand';
|
|
159
|
+
if (COMMON_CSS_PROPERTIES.includes(candidate)) return 'css-property';
|
|
160
|
+
if (ANIMATION_PRESETS.includes(candidate)) return 'animation';
|
|
161
|
+
if (BREAKPOINTS.includes(candidate)) return 'breakpoint';
|
|
162
|
+
return 'macro';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Get suggestion for invalid shorthand or property
|
|
166
|
+
export function getSuggestion(
|
|
167
|
+
prop: string,
|
|
168
|
+
validProperties: string[] = [],
|
|
169
|
+
type: 'shorthand' | 'css-property' | 'all' = 'all'
|
|
170
|
+
): string | SuggestionMatch | null {
|
|
171
|
+
// Build candidate list based on type
|
|
172
|
+
let candidates: string[] = [];
|
|
173
|
+
|
|
174
|
+
if (type === 'shorthand') {
|
|
175
|
+
candidates = KNOWN_SHORTHANDS;
|
|
176
|
+
} else if (type === 'css-property') {
|
|
177
|
+
candidates = [...COMMON_CSS_PROPERTIES, ...validProperties];
|
|
178
|
+
} else {
|
|
179
|
+
candidates = [
|
|
180
|
+
...KNOWN_SHORTHANDS,
|
|
181
|
+
...COMMON_CSS_PROPERTIES,
|
|
182
|
+
...validProperties,
|
|
183
|
+
...ANIMATION_PRESETS,
|
|
184
|
+
...BREAKPOINTS
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Remove duplicates
|
|
189
|
+
candidates = [...new Set(candidates)];
|
|
190
|
+
|
|
191
|
+
// Find best matches
|
|
192
|
+
const matches = findBestMatches(prop, candidates, 1, 3);
|
|
193
|
+
|
|
194
|
+
if (matches.length > 0) {
|
|
195
|
+
return matches[0];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Get multiple suggestions
|
|
202
|
+
export function getSuggestions(
|
|
203
|
+
prop: string,
|
|
204
|
+
validProperties: string[] = [],
|
|
205
|
+
maxResults: number = 3
|
|
206
|
+
): SuggestionMatch[] {
|
|
207
|
+
const candidates = [...new Set([
|
|
208
|
+
...KNOWN_SHORTHANDS,
|
|
209
|
+
...COMMON_CSS_PROPERTIES,
|
|
210
|
+
...validProperties,
|
|
211
|
+
...ANIMATION_PRESETS,
|
|
212
|
+
...BREAKPOINTS
|
|
213
|
+
])];
|
|
214
|
+
|
|
215
|
+
return findBestMatches(prop, candidates, maxResults, 4);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Get suggestion for CSS property with context
|
|
219
|
+
export function getPropertySuggestion(
|
|
220
|
+
prop: string,
|
|
221
|
+
context?: 'spacing' | 'color' | 'typography' | 'layout' | 'animation'
|
|
222
|
+
): string | null {
|
|
223
|
+
const contextProperties: Record<string, string[]> = {
|
|
224
|
+
spacing: [
|
|
225
|
+
'margin', 'padding', 'gap', 'width', 'height', 'top', 'right', 'bottom', 'left',
|
|
226
|
+
'inset', 'position', 'translate', 'scale', 'rotate'
|
|
227
|
+
],
|
|
228
|
+
color: [
|
|
229
|
+
'color', 'background-color', 'border-color', 'outline-color', 'fill', 'stroke',
|
|
230
|
+
'box-shadow', 'text-shadow'
|
|
231
|
+
],
|
|
232
|
+
typography: [
|
|
233
|
+
'font-family', 'font-size', 'font-weight', 'line-height', 'letter-spacing',
|
|
234
|
+
'text-align', 'text-decoration', 'text-transform', 'word-spacing'
|
|
235
|
+
],
|
|
236
|
+
layout: [
|
|
237
|
+
'display', 'position', 'flex', 'grid', 'justify-content', 'align-items',
|
|
238
|
+
'flex-direction', 'flex-wrap', 'order', 'z-index', 'overflow'
|
|
239
|
+
],
|
|
240
|
+
animation: [
|
|
241
|
+
'animation', 'transition', 'transform', 'opacity', 'filter', 'backdrop-filter',
|
|
242
|
+
'transform-origin', 'transition-property', 'transition-duration'
|
|
243
|
+
]
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const candidates = context && contextProperties[context]
|
|
247
|
+
? contextProperties[context]
|
|
248
|
+
: COMMON_CSS_PROPERTIES;
|
|
249
|
+
|
|
250
|
+
const matches = findBestMatches(prop, candidates, 1, 2);
|
|
251
|
+
|
|
252
|
+
return matches.length > 0 ? matches[0].name : null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Get shorthand suggestion with explanation
|
|
256
|
+
export function getShorthandSuggestion(shorthand: string): { suggestion: string; explanation: string } | null {
|
|
257
|
+
const shorthandMap: Record<string, { property: string; description: string }> = {
|
|
258
|
+
'm': { property: 'margin', description: 'Sets margin on all sides' },
|
|
259
|
+
'mt': { property: 'margin-top', description: 'Sets top margin' },
|
|
260
|
+
'mr': { property: 'margin-right', description: 'Sets right margin' },
|
|
261
|
+
'mb': { property: 'margin-bottom', description: 'Sets bottom margin' },
|
|
262
|
+
'ml': { property: 'margin-left', description: 'Sets left margin' },
|
|
263
|
+
'p': { property: 'padding', description: 'Sets padding on all sides' },
|
|
264
|
+
'pt': { property: 'padding-top', description: 'Sets top padding' },
|
|
265
|
+
'pr': { property: 'padding-right', description: 'Sets right padding' },
|
|
266
|
+
'pb': { property: 'padding-bottom', description: 'Sets bottom padding' },
|
|
267
|
+
'pl': { property: 'padding-left', description: 'Sets left padding' },
|
|
268
|
+
'mx': { property: 'margin-left/right', description: 'Sets horizontal margins' },
|
|
269
|
+
'my': { property: 'margin-top/bottom', description: 'Sets vertical margins' },
|
|
270
|
+
'px': { property: 'padding-left/right', description: 'Sets horizontal padding' },
|
|
271
|
+
'py': { property: 'padding-top/bottom', description: 'Sets vertical padding' },
|
|
272
|
+
'd': { property: 'display', description: 'Sets display property' },
|
|
273
|
+
'pos': { property: 'position', description: 'Sets position property' },
|
|
274
|
+
'w': { property: 'width', description: 'Sets width' },
|
|
275
|
+
'h': { property: 'height', description: 'Sets height' },
|
|
276
|
+
'bg': { property: 'background', description: 'Sets background color/image' },
|
|
277
|
+
'c': { property: 'color', description: 'Sets text color' },
|
|
278
|
+
'fs': { property: 'font-size', description: 'Sets font size' },
|
|
279
|
+
'fw': { property: 'font-weight', description: 'Sets font weight' },
|
|
280
|
+
'flex': { property: 'display: flex', description: 'Creates a flex container' },
|
|
281
|
+
'grid': { property: 'display: grid', description: 'Creates a grid container' }
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const match = shorthandMap[shorthand];
|
|
285
|
+
if (match) {
|
|
286
|
+
return {
|
|
287
|
+
suggestion: match.property,
|
|
288
|
+
explanation: match.description
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Try fuzzy match
|
|
293
|
+
const matches = findBestMatches(shorthand, Object.keys(shorthandMap), 1, 2);
|
|
294
|
+
if (matches.length > 0) {
|
|
295
|
+
const best = matches[0];
|
|
296
|
+
const matchInfo = shorthandMap[best.name];
|
|
297
|
+
if (matchInfo) {
|
|
298
|
+
return {
|
|
299
|
+
suggestion: `${best.name} → ${matchInfo.property}`,
|
|
300
|
+
explanation: matchInfo.description
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Validate and suggest fix for CSS value
|
|
309
|
+
export function getValueSuggestion(
|
|
310
|
+
property: string,
|
|
311
|
+
value: string
|
|
312
|
+
): { suggested: string; confidence: number } | null {
|
|
313
|
+
// Common value corrections
|
|
314
|
+
const corrections: Record<string, Record<string, string>> = {
|
|
315
|
+
'display': {
|
|
316
|
+
'flexbox': 'flex',
|
|
317
|
+
'inline-flexbox': 'inline-flex',
|
|
318
|
+
'gridbox': 'grid',
|
|
319
|
+
'block-level': 'block',
|
|
320
|
+
'inline-level': 'inline'
|
|
321
|
+
},
|
|
322
|
+
'position': {
|
|
323
|
+
'static': 'static',
|
|
324
|
+
'relative': 'relative',
|
|
325
|
+
'absolute': 'absolute',
|
|
326
|
+
'fixed': 'fixed',
|
|
327
|
+
'sticky': 'sticky'
|
|
328
|
+
},
|
|
329
|
+
'text-align': {
|
|
330
|
+
'center': 'center',
|
|
331
|
+
'left': 'left',
|
|
332
|
+
'right': 'right',
|
|
333
|
+
'justify': 'justify'
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const propertyCorrections = corrections[property];
|
|
338
|
+
if (propertyCorrections) {
|
|
339
|
+
const lowerValue = value.toLowerCase();
|
|
340
|
+
for (const [wrong, correct] of Object.entries(propertyCorrections)) {
|
|
341
|
+
if (lowerValue === wrong) {
|
|
342
|
+
return { suggested: correct, confidence: 0.95 };
|
|
343
|
+
}
|
|
344
|
+
if (lowerValue.includes(wrong)) {
|
|
345
|
+
return { suggested: correct, confidence: 0.7 };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Get all available suggestions for autocomplete
|
|
354
|
+
export function getAutocompleteSuggestions(prefix: string = '', limit: number = 10): SuggestionMatch[] {
|
|
355
|
+
const allSuggestions = [
|
|
356
|
+
...KNOWN_SHORTHANDS.map(s => ({ name: s, type: 'shorthand' as const, distance: 0 })),
|
|
357
|
+
...COMMON_CSS_PROPERTIES.map(s => ({ name: s, type: 'css-property' as const, distance: 0 })),
|
|
358
|
+
...ANIMATION_PRESETS.map(s => ({ name: s, type: 'animation' as const, distance: 0 })),
|
|
359
|
+
...BREAKPOINTS.map(s => ({ name: s, type: 'breakpoint' as const, distance: 0 }))
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
if (!prefix) {
|
|
363
|
+
return allSuggestions.slice(0, limit);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const lowerPrefix = prefix.toLowerCase();
|
|
367
|
+
const matches = allSuggestions
|
|
368
|
+
.filter(s => s.name.toLowerCase().startsWith(lowerPrefix))
|
|
369
|
+
.slice(0, limit);
|
|
370
|
+
|
|
371
|
+
if (matches.length < limit) {
|
|
372
|
+
// Also include fuzzy matches
|
|
373
|
+
const fuzzyMatches = findBestMatches(prefix, allSuggestions.map(s => s.name), limit - matches.length);
|
|
374
|
+
for (const match of fuzzyMatches) {
|
|
375
|
+
if (!matches.some(m => m.name === match.name)) {
|
|
376
|
+
matches.push(match as any);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return matches;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Format suggestion for console output
|
|
385
|
+
export function formatSuggestion(suggestion: SuggestionMatch): string {
|
|
386
|
+
const typeColors: Record<string, string> = {
|
|
387
|
+
'shorthand': '🟢',
|
|
388
|
+
'css-property': '🔵',
|
|
389
|
+
'macro': '🟣',
|
|
390
|
+
'animation': '🎬',
|
|
391
|
+
'breakpoint': '📱'
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const icon = typeColors[suggestion.type] || '⚪';
|
|
395
|
+
return `${icon} ${suggestion.name} (${suggestion.type}, distance: ${suggestion.distance})`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Get suggestion with full details
|
|
399
|
+
export function getDetailedSuggestion(prop: string, validProperties: string[] = []): {
|
|
400
|
+
suggestion: string | null;
|
|
401
|
+
alternatives: SuggestionMatch[];
|
|
402
|
+
type: string;
|
|
403
|
+
confidence: number;
|
|
404
|
+
} | null {
|
|
405
|
+
const suggestion = getSuggestion(prop, validProperties);
|
|
406
|
+
|
|
407
|
+
if (!suggestion) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const match = typeof suggestion === 'object' ? suggestion : { name: suggestion, distance: 1, type: getTypeForCandidate(suggestion) };
|
|
412
|
+
const confidence = 1 - (match.distance / Math.max(prop.length, match.name.length));
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
suggestion: match.name,
|
|
416
|
+
alternatives: getSuggestions(prop, validProperties, 3),
|
|
417
|
+
type: match.type,
|
|
418
|
+
confidence: Math.max(0, Math.min(1, confidence))
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Export default
|
|
423
|
+
export default {
|
|
424
|
+
getSuggestion,
|
|
425
|
+
getSuggestions,
|
|
426
|
+
getPropertySuggestion,
|
|
427
|
+
getShorthandSuggestion,
|
|
428
|
+
getValueSuggestion,
|
|
429
|
+
getAutocompleteSuggestions,
|
|
430
|
+
formatSuggestion,
|
|
431
|
+
getDetailedSuggestion,
|
|
432
|
+
KNOWN_SHORTHANDS,
|
|
433
|
+
COMMON_CSS_PROPERTIES,
|
|
434
|
+
ANIMATION_PRESETS,
|
|
435
|
+
BREAKPOINTS
|
|
436
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// src/compiler/theme-contract.ts
|
|
2
|
+
/**
|
|
3
|
+
* Theme Contract System for ChainCSS
|
|
4
|
+
* Validates that themes match the expected shape
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ThemeContract {
|
|
8
|
+
[key: string]: ThemeContract | string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ThemeTokens {
|
|
12
|
+
[key: string]: string | ThemeTokens;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Theme class with getter method
|
|
17
|
+
*/
|
|
18
|
+
export class Theme {
|
|
19
|
+
private tokens: ThemeTokens;
|
|
20
|
+
|
|
21
|
+
constructor(tokens: ThemeTokens) {
|
|
22
|
+
this.tokens = tokens;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get(path: string): string | undefined {
|
|
26
|
+
const parts = path.split('.');
|
|
27
|
+
let current: any = this.tokens;
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
if (current === undefined || current === null) return undefined;
|
|
30
|
+
current = current[part];
|
|
31
|
+
}
|
|
32
|
+
return typeof current === 'string' ? current : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
set(path: string, value: string): void {
|
|
36
|
+
const parts = path.split('.');
|
|
37
|
+
let current: any = this.tokens;
|
|
38
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
39
|
+
if (!current[parts[i]]) {
|
|
40
|
+
current[parts[i]] = {};
|
|
41
|
+
}
|
|
42
|
+
current = current[parts[i]];
|
|
43
|
+
}
|
|
44
|
+
current[parts[parts.length - 1]] = value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
toObject(): ThemeTokens {
|
|
48
|
+
return this.tokens;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
toCSSVariables(prefix: string = 'theme'): string {
|
|
52
|
+
let css = '';
|
|
53
|
+
const flatten = (obj: ThemeTokens, path: string = '') => {
|
|
54
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
55
|
+
const newPath = path ? `${path}-${key}` : key;
|
|
56
|
+
if (typeof value === 'object' && value !== null) {
|
|
57
|
+
flatten(value, newPath);
|
|
58
|
+
} else {
|
|
59
|
+
css += ` --${prefix}-${newPath}: ${value};\n`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
flatten(this.tokens);
|
|
64
|
+
return `:root {\n${css}}\n`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create a theme contract that defines the expected shape of themes
|
|
70
|
+
*/
|
|
71
|
+
export function createThemeContract<T extends ThemeContract>(contractShape: T): T & {
|
|
72
|
+
__isContract: true;
|
|
73
|
+
__validate: (theme: ThemeTokens) => boolean;
|
|
74
|
+
} {
|
|
75
|
+
const contract = contractShape;
|
|
76
|
+
|
|
77
|
+
const contractProxy = Object.assign({}, contract, {
|
|
78
|
+
__isContract: true as const,
|
|
79
|
+
__validate: (theme: ThemeTokens) => validateTheme(contract, theme)
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return contractProxy as T & {
|
|
83
|
+
__isContract: true;
|
|
84
|
+
__validate: (theme: ThemeTokens) => boolean;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate that a theme matches the contract
|
|
90
|
+
* @returns true if valid, throws error otherwise
|
|
91
|
+
*/
|
|
92
|
+
export function validateTheme(
|
|
93
|
+
contract: ThemeContract,
|
|
94
|
+
theme: ThemeTokens = {},
|
|
95
|
+
path: string = ''
|
|
96
|
+
): boolean {
|
|
97
|
+
const errors: string[] = [];
|
|
98
|
+
|
|
99
|
+
function validate(
|
|
100
|
+
contractPart: ThemeContract,
|
|
101
|
+
themePart: ThemeTokens | undefined,
|
|
102
|
+
currentPath: string
|
|
103
|
+
): void {
|
|
104
|
+
if (typeof contractPart === 'object' && contractPart !== null) {
|
|
105
|
+
const requiredKeys = Object.keys(contractPart);
|
|
106
|
+
const themeKeys = Object.keys(themePart || {});
|
|
107
|
+
|
|
108
|
+
requiredKeys.forEach(key => {
|
|
109
|
+
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
110
|
+
|
|
111
|
+
if (!themePart || !(key in themePart)) {
|
|
112
|
+
errors.push(` ✗ Missing required token: "${newPath}"`);
|
|
113
|
+
} else {
|
|
114
|
+
validate(
|
|
115
|
+
contractPart[key] as ThemeContract,
|
|
116
|
+
themePart[key] as ThemeTokens | undefined,
|
|
117
|
+
newPath
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
themeKeys.forEach(key => {
|
|
123
|
+
if (!(key in contractPart)) {
|
|
124
|
+
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
|
125
|
+
console.warn(`⚠️ Extra token not in contract: "${newPath}"`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
if (themePart !== undefined && typeof themePart !== 'string') {
|
|
130
|
+
errors.push(` ✗ Token "${currentPath}" must be a string, got ${typeof themePart}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
validate(contract, theme, path);
|
|
136
|
+
|
|
137
|
+
if (errors.length > 0) {
|
|
138
|
+
throw new Error(`Theme Contract Validation Failed:\n${errors.join('\n')}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create an actual theme from a contract and values
|
|
146
|
+
*/
|
|
147
|
+
export function createTheme<T extends ThemeContract>(
|
|
148
|
+
contract: T | (T & { __isContract: boolean }),
|
|
149
|
+
themeValues: ThemeTokens
|
|
150
|
+
): Theme {
|
|
151
|
+
// Validate if contract has validation method
|
|
152
|
+
if (typeof (contract as any).__validate === 'function') {
|
|
153
|
+
(contract as any).__validate(themeValues);
|
|
154
|
+
} else {
|
|
155
|
+
validateTheme(contract as T, themeValues);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const tokens: ThemeTokens = {};
|
|
159
|
+
|
|
160
|
+
function buildTokens(
|
|
161
|
+
contractPart: T,
|
|
162
|
+
themePart: ThemeTokens | undefined,
|
|
163
|
+
target: ThemeTokens,
|
|
164
|
+
_path: string = ''
|
|
165
|
+
): void {
|
|
166
|
+
Object.keys(contractPart).forEach(key => {
|
|
167
|
+
if (typeof contractPart[key] === 'object' && contractPart[key] !== null) {
|
|
168
|
+
target[key] = {};
|
|
169
|
+
buildTokens(
|
|
170
|
+
contractPart[key] as T,
|
|
171
|
+
(themePart?.[key] as ThemeTokens) || {},
|
|
172
|
+
target[key] as ThemeTokens,
|
|
173
|
+
_path
|
|
174
|
+
);
|
|
175
|
+
} else {
|
|
176
|
+
target[key] = themePart?.[key] as string;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
buildTokens(contract as T, themeValues, tokens);
|
|
182
|
+
|
|
183
|
+
return new Theme(tokens);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Type guard to check if an object is a theme contract
|
|
187
|
+
export function isThemeContract(obj: any): obj is ThemeContract & { __isContract: true } {
|
|
188
|
+
return obj && typeof obj === 'object' && obj.__isContract === true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default {
|
|
192
|
+
Theme,
|
|
193
|
+
createThemeContract,
|
|
194
|
+
validateTheme,
|
|
195
|
+
createTheme,
|
|
196
|
+
isThemeContract
|
|
197
|
+
};
|