chaincss 2.4.3 → 2.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +275 -589
- package/dist/plugins/vite.d.ts +2 -3
- package/dist/plugins/vite.js +3 -0
- package/package.json +1 -1
- package/src/plugins/vite.ts +3 -8
package/README.md
CHANGED
|
@@ -1,678 +1,364 @@
|
|
|
1
|
-
|
|
1
|
+
# ChainCSS v2.4
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<strong>The CSS-in-JS platform with compiler intelligence.</strong><br>
|
|
5
|
-
Zero runtime by default. Semantic styling. WCAG-aware. Intent-driven.
|
|
6
|
-
</p>
|
|
3
|
+
**Zero-runtime CSS-in-JS with auto-detection.** Static styles compile to plain CSS. Dynamic values resolve at runtime. No manual mode switching.
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
</a>
|
|
12
|
-
|
|
13
|
-
<a href="https://github.com/melcanz08/chaincss/blob/main/LICENSE">
|
|
14
|
-
<img src="https://img.shields.io/github/license/melcanz08/chaincss" alt="license">
|
|
15
|
-
</a>
|
|
16
|
-
|
|
17
|
-
<a href="https://github.com/melcanz08/chaincss/actions">
|
|
18
|
-
<img src="https://img.shields.io/badge/tests-708%20passed-brightgreen" alt="tests">
|
|
19
|
-
</a>
|
|
20
|
-
|
|
21
|
-
<a href="https://github.com/melcanz08/chaincss">
|
|
22
|
-
<img src="https://img.shields.io/badge/modules-17-blue" alt="modules">
|
|
23
|
-
</a>
|
|
24
|
-
</p>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# What is ChainCSS?
|
|
29
|
-
|
|
30
|
-
ChainCSS lets you write styles as **native JavaScript method chains** — no CSS syntax, no template literals, no object literals.
|
|
31
|
-
|
|
32
|
-
It automatically detects which styles are static (compiled to zero-runtime CSS) and which are dynamic (stay in JS), then splits them automatically.
|
|
5
|
+
```bash
|
|
6
|
+
npm install chaincss
|
|
7
|
+
```
|
|
33
8
|
|
|
34
|
-
|
|
9
|
+
Quick Start
|
|
10
|
+
### tsx
|
|
35
11
|
|
|
36
|
-
|
|
12
|
+
```tsx
|
|
13
|
+
import { chain } from 'chaincss';
|
|
37
14
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
.$el("card");
|
|
46
|
-
|
|
47
|
-
// Or write explicit styles:
|
|
48
|
-
const hero = chain()
|
|
49
|
-
.display("flex")
|
|
50
|
-
.flexDirection("column")
|
|
51
|
-
.gap(16)
|
|
52
|
-
.padding(24)
|
|
53
|
-
.background("white")
|
|
54
|
-
.borderRadius(12)
|
|
15
|
+
const btn = chain()
|
|
16
|
+
.bg('#6366f1')
|
|
17
|
+
.color('#ffffff')
|
|
18
|
+
.padding('12px 24px')
|
|
19
|
+
.rounded(8)
|
|
20
|
+
.fontSize(16)
|
|
21
|
+
.fontWeight(600)
|
|
55
22
|
.hover()
|
|
56
|
-
.
|
|
57
|
-
.transform(
|
|
23
|
+
.bg('#4f46e5')
|
|
24
|
+
.transform('translateY(-2px)')
|
|
58
25
|
.end()
|
|
59
|
-
.$el(
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**No CSS syntax. No template literals. Compiler-enforced quality.**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# Constraint-Based Styling
|
|
66
|
-
|
|
67
|
-
Declare relationships, not values. The constraint solver compiles them to native CSS at build time.
|
|
68
|
-
|
|
69
|
-
**No runtime JS. No manual `calc()`. The compiler picks the best CSS feature.**
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## 1. Aspect Ratios from Math
|
|
74
|
-
|
|
75
|
-
Describe the relationship between width and height:
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
chain()
|
|
79
|
-
.constrain("height", "= width * 0.5") // 2:1 ratio
|
|
80
|
-
.constrain("width", "< parent") // Don't overflow container
|
|
81
|
-
.$el("video");
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Compiles to
|
|
26
|
+
.$el('button');
|
|
85
27
|
|
|
86
|
-
|
|
87
|
-
.
|
|
88
|
-
|
|
89
|
-
max-width: 100%;
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
The solver detects `width * 0.5` and emits `aspect-ratio` instead of:
|
|
94
|
-
|
|
95
|
-
```css
|
|
96
|
-
height: calc(width * 0.5);
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`aspect-ratio` has better performance and avoids layout shifts.
|
|
100
|
-
|
|
101
|
-
### Tailwind Equivalent
|
|
102
|
-
|
|
103
|
-
```txt
|
|
104
|
-
aspect-[2/1] max-w-full
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### ChainCSS Difference
|
|
108
|
-
|
|
109
|
-
You write the math. The compiler writes the CSS.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## 2. Container-Aware Responsive Layouts
|
|
114
|
-
|
|
115
|
-
Use conditional constraints to generate `@container` queries automatically.
|
|
116
|
-
|
|
117
|
-
```ts
|
|
118
|
-
chain()
|
|
119
|
-
.constrain("columns", ">= 3 when > 768px")
|
|
120
|
-
.constrain("gap", "= 24px")
|
|
121
|
-
.$el("grid");
|
|
28
|
+
// { selectors: ['.chain-button'], backgroundColor: '#6366f1', ... }
|
|
29
|
+
// CSS output: .chain-button { background-color: #6366f1; ... }
|
|
30
|
+
// CSS output: .chain-button:hover { background-color: #4f46e5; ... }
|
|
122
31
|
```
|
|
123
32
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
```css
|
|
127
|
-
.grid {
|
|
128
|
-
gap: 24px;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
@container (min-width: 768px) {
|
|
132
|
-
.grid {
|
|
133
|
-
columns: 3;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
> Requires `container-type: inline-size` on the parent.
|
|
139
|
-
> ChainCSS adds it automatically when container constraints are detected.
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## 3. Fluid Values with `clamp()`
|
|
33
|
+
Auto-Detection: Static vs Dynamic
|
|
34
|
+
ChainCSS automatically detects what can be compiled at build time and what needs runtime resolution. You never specify which is which.
|
|
144
35
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.
|
|
150
|
-
.
|
|
151
|
-
.$el(
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Compiles to
|
|
155
|
-
|
|
156
|
-
```css
|
|
157
|
-
.hero {
|
|
158
|
-
font-size: clamp(14px, 5vw, 24px);
|
|
159
|
-
padding: clamp(16px, 4vw, 32px);
|
|
160
|
-
}
|
|
36
|
+
```tsx
|
|
37
|
+
const styles = chain()
|
|
38
|
+
.bg('#6366f1') // static -> goes to CSS file
|
|
39
|
+
.color(() => isActive ? 'green' : 'red') // dynamic -> resolved at runtime
|
|
40
|
+
.padding(16) // static -> goes to CSS file
|
|
41
|
+
.border(() => isActive ? '2px solid green' : '1px solid gray') // dynamic
|
|
42
|
+
.$el('btn');
|
|
161
43
|
```
|
|
44
|
+
Rule: Strings and numbers are static. Functions are dynamic.
|
|
162
45
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
- `min()`
|
|
167
|
-
- `max()`
|
|
168
|
-
- `calc()`
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## 4. Scroll-Driven Sticky Positioning
|
|
173
|
-
|
|
174
|
-
"Stick this element until another element reaches the viewport."
|
|
46
|
+
API
|
|
47
|
+
### chain()
|
|
48
|
+
Creates a new style chain. Returns a proxy that collects styles.
|
|
175
49
|
|
|
176
50
|
```ts
|
|
177
|
-
chain
|
|
178
|
-
.constrain("sidebar", "sticky until footer")
|
|
179
|
-
.$el("sidebar");
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Compiles to
|
|
51
|
+
import { chain } from 'chaincss';
|
|
183
52
|
|
|
184
|
-
|
|
185
|
-
.
|
|
186
|
-
|
|
187
|
-
top: 0;
|
|
188
|
-
|
|
189
|
-
animation: sticky-sidebar 1s linear both;
|
|
190
|
-
animation-timeline: scroll();
|
|
191
|
-
animation-range: contain 0% contain 100%;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
@keyframes sticky-sidebar {
|
|
195
|
-
to {
|
|
196
|
-
position: relative;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
53
|
+
const styles = chain()
|
|
54
|
+
.property(value) // any CSS property (camelCase)
|
|
55
|
+
.$el('name'); // finalize, returns { selectors: ['.chain-name'], ...properties }
|
|
199
56
|
```
|
|
200
57
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## 5. Parent-Relative Constraints
|
|
206
|
-
|
|
207
|
-
Keep elements inside their parents without writing media queries.
|
|
58
|
+
Properties
|
|
59
|
+
All 500+ CSS properties are available as camelCase methods:
|
|
208
60
|
|
|
209
61
|
```ts
|
|
210
62
|
chain()
|
|
211
|
-
.
|
|
212
|
-
.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
|
232
|
-
|
|
233
|
-
|
|
|
234
|
-
|
|
|
235
|
-
|
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
console.log(result.explanation);
|
|
298
|
-
// "height = width * 0.5 → aspect-ratio: 1/2"
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
# What Makes ChainCSS Different
|
|
303
|
-
|
|
304
|
-
| Capability | ChainCSS | Tailwind | StyleX | Vanilla Extract |
|
|
305
|
-
|---|---|---|---|---|
|
|
306
|
-
| Zero runtime | ✅ | ✅ | ✅ | ✅ |
|
|
307
|
-
| Intent-based API | ✅ | ❌ | ❌ | ❌ |
|
|
308
|
-
| Semantic tokens | ✅ | ❌ | ❌ | ❌ |
|
|
309
|
-
| WCAG accessibility checking | ✅ | ❌ | ❌ | ❌ |
|
|
310
|
-
| Responsive inference | ✅ | ❌ | ❌ | ❌ |
|
|
311
|
-
| Pattern learning | ✅ | ❌ | ❌ | ❌ |
|
|
312
|
-
| CSS `if()` transpiler | ✅ | ❌ | ❌ | ❌ |
|
|
313
|
-
| Constraint-based styling | ✅ | ❌ | ❌ | ❌ |
|
|
314
|
-
| Layout intelligence | ✅ | ❌ | ❌ | ❌ |
|
|
315
|
-
| Scroll-driven animations | ✅ | ❌ | ❌ | ❌ |
|
|
316
|
-
| Self-healing CSS | ✅ | ❌ | ❌ | ❌ |
|
|
317
|
-
| Source-aware optimization | ✅ | ❌ | ❌ | ❌ |
|
|
318
|
-
| Design system extraction | ✅ | ❌ | ❌ | ❌ |
|
|
319
|
-
| 3 modes (build/runtime/hybrid) | ✅ | ❌ | ❌ | ❌ |
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
# Installation
|
|
324
|
-
|
|
325
|
-
```bash
|
|
326
|
-
npm install chaincss
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
# Quick Start
|
|
331
|
-
|
|
332
|
-
| Environment | Setup |
|
|
333
|
-
|---|---|
|
|
334
|
-
| Vite | Add `chaincss()` plugin to `vite.config.ts` |
|
|
335
|
-
| Node.js | `import { chain } from "chaincss"` |
|
|
336
|
-
| Browser CDN | `import { chain } from "https://cdn.jsdelivr.net/npm/chaincss/dist/browser.js"` |
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
# Intent API (v2.3)
|
|
340
|
-
|
|
341
|
-
The highest-level API.
|
|
342
|
-
|
|
343
|
-
Write what you want — the compiler figures out how.
|
|
344
|
-
|
|
345
|
-
```ts
|
|
346
|
-
// Layout intents
|
|
347
|
-
chain().intent("center-content").$el("centered");
|
|
348
|
-
chain().intent("stack").$el("stack");
|
|
349
|
-
chain().intent("sidebar-layout").$el("dashboard");
|
|
350
|
-
|
|
351
|
-
// Component intents
|
|
352
|
-
chain().intent("card").$el("card");
|
|
353
|
-
chain().intent("button-primary").$el("cta");
|
|
354
|
-
chain().intent("modal").$el("dialog");
|
|
355
|
-
|
|
356
|
-
// Semantic intents
|
|
357
|
-
chain().intent("hero-section").$el("hero");
|
|
358
|
-
chain().intent("sticky-header").$el("nav");
|
|
359
|
-
|
|
360
|
-
// Interaction intents
|
|
361
|
-
chain().intent("hover-lift").$el("interactive");
|
|
362
|
-
chain().intent("focus-ring").$el("accessible");
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
Each intent triggers:
|
|
366
|
-
- semantic tokens
|
|
367
|
-
- responsive overrides
|
|
368
|
-
- accessibility checks
|
|
369
|
-
- theme adaptation
|
|
370
|
-
|
|
371
|
-
—all at build time.
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
# Semantic Tokens
|
|
63
|
+
.backgroundColor('#fff')
|
|
64
|
+
.fontSize(16)
|
|
65
|
+
.borderRadius(8)
|
|
66
|
+
.display('flex')
|
|
67
|
+
.alignItems('center')
|
|
68
|
+
.justifyContent('space-between')
|
|
69
|
+
```
|
|
70
|
+
Numeric values automatically get px added (except unitless properties like opacity, zIndex, fontWeight).
|
|
71
|
+
|
|
72
|
+
Shorthands
|
|
73
|
+
Common properties have short aliases:
|
|
74
|
+
|
|
75
|
+
| Shorthand | CSS Property |
|
|
76
|
+
| :--- | :--- |
|
|
77
|
+
| bg() | background-color |
|
|
78
|
+
| fs() | font-size |
|
|
79
|
+
| fw() | font-weight |
|
|
80
|
+
| rounded() | border-radius |
|
|
81
|
+
| p() | padding |
|
|
82
|
+
| m() | margin |
|
|
83
|
+
| px() / py() | padding-left/right / padding-top/bottom |
|
|
84
|
+
| mx() / my() | margin-left/right / margin-top/bottom |
|
|
85
|
+
| flex() | display: flex |
|
|
86
|
+
| grid() | display: grid |
|
|
87
|
+
| inlineFlex() | display: inline-flex |
|
|
88
|
+
| block() | display: block |
|
|
89
|
+
| w() / h() | width / height |
|
|
90
|
+
| maxW() / minH() | max-width / min-height |
|
|
91
|
+
| lh() | line-height |
|
|
92
|
+
| ls() | letter-spacing |
|
|
93
|
+
| ov() | overflow |
|
|
94
|
+
| pos() | position |
|
|
95
|
+
| z() | z-index |
|
|
96
|
+
| op() | opacity |
|
|
97
|
+
| gap() | gap |
|
|
98
|
+
| gridCols() | grid-template-columns |
|
|
99
|
+
| align() | text-align |
|
|
100
|
+
| cursor() | cursor |
|
|
101
|
+
| shadow() | box-shadow |
|
|
102
|
+
| transform() | transform |
|
|
103
|
+
| transition() | transition |
|
|
104
|
+
|
|
105
|
+
Macros
|
|
106
|
+
One method replaces multiple CSS declarations:
|
|
107
|
+
|
|
108
|
+
| Macro | What it does |
|
|
109
|
+
| :--- | :--- |
|
|
110
|
+
| center() | display: flex; align-items: center; justify-content: center |
|
|
111
|
+
| flexCenter(dir?) | Flex centering, optional 'row' or 'col' direction |
|
|
112
|
+
| gridCenter() | display: grid; place-items: center |
|
|
113
|
+
| pill() | border-radius: 9999px; padding: 8px 20px; display: inline-flex; align-items: center |
|
|
114
|
+
| circle(size) | Perfect circle with flex centering |
|
|
115
|
+
| square(size) | Square with flex centering |
|
|
116
|
+
| glass(blur?) | Backdrop blur glassmorphism effect |
|
|
117
|
+
| glow({color, size}) | Box-shadow glow effect |
|
|
118
|
+
| hide() | opacity: 0; visibility: hidden; pointer-events: none |
|
|
119
|
+
| show() | opacity: 1; visibility: visible; pointer-events: auto |
|
|
120
|
+
| truncate() | overflow: hidden; text-overflow: ellipsis; white-space: nowrap |
|
|
121
|
+
| textGradient(colors) | Gradient text with webkit background clip |
|
|
122
|
+
| meshGradient(colors) | Multi-color mesh gradient background |
|
|
123
|
+
| absolute(coords?) | position: absolute with optional top/right/bottom/left |
|
|
124
|
+
| fixed(coords?) | position: fixed with optional coordinates |
|
|
125
|
+
| sticky(coords?) | position: sticky with optional coordinates |
|
|
126
|
+
| relative() | position: relative |
|
|
127
|
+
| size(value) | Sets both width and height |
|
|
128
|
+
| stack({spacing, dir?}) | Flex column with configurable spacing and direction |
|
|
129
|
+
| gridTable(minWidth) | Responsive auto-fit grid table |
|
|
130
|
+
| aspect(ratio) | Set aspect-ratio (supports 'square', 'video', 'golden') |
|
|
131
|
+
| safeArea(edge?) | iOS safe area padding |
|
|
132
|
+
| clickScale(amount?) | Scale down on :active pseudo-class |
|
|
133
|
+
| pressable() | Combines cursor: pointer + unselectable + clickScale |
|
|
134
|
+
| focusRing(color?) | Focus-visible outline ring |
|
|
135
|
+
| skeleton({active, color?}) | Loading skeleton animation |
|
|
136
|
+
| shimmer() | Shimmer loading animation |
|
|
137
|
+
| fluidText({min, max, vw?}) | Responsive fluid typography using clamp() |
|
|
138
|
+
| lineClamp(lines?) | Multi-line text truncation |
|
|
139
|
+
| frostedNav(blur?) | Fixed navbar with glass effect + safe area |
|
|
140
|
+
| parallax(scale?) | Parallax scrolling container |
|
|
141
|
+
| noise(opacity?) | SVG noise texture background |
|
|
142
|
+
| scrollable(axis?) | Scrollable container ('x', 'y', 'both') |
|
|
143
|
+
| unselectable() | Disable text selection |
|
|
144
|
+
| outlineDebug() | Red outline debugging for layout |
|
|
145
|
+
|
|
146
|
+
States & Selectors
|
|
376
147
|
|
|
377
148
|
```ts
|
|
378
149
|
chain()
|
|
379
|
-
.
|
|
380
|
-
|
|
381
|
-
.
|
|
382
|
-
.
|
|
383
|
-
.
|
|
384
|
-
.
|
|
385
|
-
|
|
150
|
+
.hover()
|
|
151
|
+
.bg('red')
|
|
152
|
+
.end()
|
|
153
|
+
.nest('.child', (c) => c.color('blue'))
|
|
154
|
+
.children((c) => c.padding(8)) // shortcut for nest('& > *', ...)
|
|
155
|
+
.media('(min-width: 768px)', (c) => c.flexDirection('row'))
|
|
156
|
+
.supports('display: grid', (c) => c.gap(16))
|
|
157
|
+
.container('(min-width: 400px)', (c) => c.color('red'))
|
|
158
|
+
.layer('base', (c) => c.bg('white'))
|
|
159
|
+
.when(condition, (c) => c.display('none'))
|
|
386
160
|
```
|
|
387
161
|
|
|
388
|
-
|
|
389
|
-
|---|---|
|
|
390
|
-
| surface | interactive, container, overlay, sheet |
|
|
391
|
-
| text | primary, secondary, muted, link |
|
|
392
|
-
| elevation | flat, raised, floating, modal |
|
|
393
|
-
| spacing | none, tight, compact, spacious |
|
|
394
|
-
| state | hover, active, focus, disabled |
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
# Compiler Intelligence
|
|
399
|
-
|
|
400
|
-
ChainCSS analyzes your styles at build time and reports issues before they ship.
|
|
162
|
+
Transforms
|
|
401
163
|
|
|
402
164
|
```ts
|
|
403
165
|
chain()
|
|
404
|
-
.
|
|
405
|
-
.
|
|
406
|
-
.
|
|
407
|
-
.
|
|
408
|
-
.
|
|
409
|
-
.$el("risky-button");
|
|
166
|
+
.scale(1.2)
|
|
167
|
+
.rotate('45deg')
|
|
168
|
+
.x(10) // translateX with automatic px
|
|
169
|
+
.y(20) // translateY with automatic px
|
|
170
|
+
.skew('5deg')
|
|
410
171
|
```
|
|
411
172
|
|
|
412
|
-
|
|
413
|
-
- detect WCAG contrast failures
|
|
414
|
-
- fix invalid font sizes
|
|
415
|
-
- add focus-visible fallbacks
|
|
416
|
-
- wrap animations in reduced-motion queries
|
|
417
|
-
- prevent mobile overflow
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
# Constraint-Based Styling
|
|
173
|
+
Keyframes & Fonts
|
|
422
174
|
|
|
423
175
|
```ts
|
|
424
176
|
chain()
|
|
425
|
-
.
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
177
|
+
.keyframes('fadeIn', {
|
|
178
|
+
'0%': { opacity: 0 },
|
|
179
|
+
'100%': { opacity: 1 }
|
|
180
|
+
})
|
|
181
|
+
.fontFace({ fontFamily: 'MyFont', src: 'url(/myfont.woff2)' })
|
|
429
182
|
```
|
|
430
183
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
# Scroll Timeline Engine
|
|
184
|
+
Mixins
|
|
434
185
|
|
|
435
186
|
```ts
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
compileScrollAnimation
|
|
439
|
-
} from "chaincss";
|
|
440
|
-
|
|
441
|
-
const fadeIn = createScrollAnimation(
|
|
442
|
-
"fadeIn",
|
|
443
|
-
".reveal"
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
const parallax = createScrollAnimation(
|
|
447
|
-
"parallax",
|
|
448
|
-
".bg"
|
|
449
|
-
);
|
|
187
|
+
const mixin = { color: 'red', fontSize: '16px' };
|
|
188
|
+
chain().use(mixin).bg('blue').$el('test');
|
|
450
189
|
```
|
|
451
190
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
# Core API
|
|
457
|
-
|
|
458
|
-
## The Chain
|
|
191
|
+
Compiling to CSS
|
|
459
192
|
|
|
460
193
|
```ts
|
|
461
|
-
import { chain } from
|
|
194
|
+
import { chain, compileToCSS } from 'chaincss';
|
|
462
195
|
|
|
463
196
|
const styles = chain()
|
|
464
|
-
.
|
|
465
|
-
.padding(
|
|
466
|
-
.
|
|
467
|
-
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
## Smart Chain
|
|
197
|
+
.bg('red')
|
|
198
|
+
.padding(16)
|
|
199
|
+
.hover().bg('darkred').end()
|
|
200
|
+
.build(['button']);
|
|
471
201
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const styles = smartChain()
|
|
476
|
-
.display("flex")
|
|
477
|
-
.padding(20)
|
|
478
|
-
.color(props.textColor)
|
|
479
|
-
.fontSize(theme.sizes.lg)
|
|
480
|
-
.$el("hybrid-card");
|
|
202
|
+
const css = compileToCSS(styles, { scopeSelector: '.btn' });
|
|
203
|
+
// .btn { background-color: red; padding: 16px; }
|
|
204
|
+
// .btn:hover { background-color: darkred; }
|
|
481
205
|
```
|
|
482
206
|
|
|
207
|
+
`compileToCSS(styleObject, options?)`
|
|
483
208
|
|
|
209
|
+
| Option | Type | Default | Description |
|
|
210
|
+
| :--- | :--- | :--- | :--- |
|
|
211
|
+
| scopeSelector | string | '' | CSS selector for the rule |
|
|
212
|
+
| minify | boolean | false | Minify output |
|
|
213
|
+
| sourceMap | boolean | false | Add source comments |
|
|
214
|
+
| sourceFile | string | '' | Source file path for comments |
|
|
484
215
|
|
|
485
|
-
|
|
216
|
+
`partitionForBuild(styleObject, options?)`
|
|
217
|
+
Splits styles into static CSS and dynamic values for build-time extraction:
|
|
486
218
|
|
|
487
219
|
```ts
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
.br(8)
|
|
493
|
-
.fs(16)
|
|
494
|
-
.fw(700)
|
|
495
|
-
.c("#333");
|
|
220
|
+
const { css, dynamicValues, hasDynamic } = partitionForBuild(styles, { scopeSelector: '.btn' });
|
|
221
|
+
// css: '.btn { background-color: red; }'
|
|
222
|
+
// dynamicValues: { color: <function> }
|
|
223
|
+
// hasDynamic: true
|
|
496
224
|
```
|
|
497
225
|
|
|
498
|
-
|
|
499
|
-
# Macros
|
|
226
|
+
Vite Plugin
|
|
500
227
|
|
|
501
228
|
```ts
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
# Conditional Styles
|
|
514
|
-
|
|
515
|
-
```ts
|
|
516
|
-
chain()
|
|
517
|
-
.padding(12)
|
|
518
|
-
.when(isActive, c =>
|
|
519
|
-
c.background("#10b981")
|
|
520
|
-
.color("white")
|
|
521
|
-
)
|
|
522
|
-
.$el("stateful-btn");
|
|
229
|
+
// vite.config.ts
|
|
230
|
+
import chaincssPlugin from 'chaincss/plugin/vite';
|
|
231
|
+
import react from '@vitejs/plugin-react';
|
|
232
|
+
|
|
233
|
+
export default defineConfig({
|
|
234
|
+
plugins: [
|
|
235
|
+
chaincssPlugin({ verbose: true }),
|
|
236
|
+
react(),
|
|
237
|
+
],
|
|
238
|
+
});
|
|
523
239
|
```
|
|
240
|
+
The plugin:
|
|
241
|
+
* Serves generated CSS at `/__chaincss.css`
|
|
242
|
+
* Auto-injects `<link>` tag into HTML
|
|
243
|
+
* Watches for CSS changes and hot-reloads
|
|
524
244
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
# Responsive Design
|
|
245
|
+
Setup
|
|
246
|
+
Create a CSS generation script and add it to your package.json:
|
|
528
247
|
|
|
529
248
|
```ts
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
c.flexDirection("row")
|
|
535
|
-
)
|
|
536
|
-
.$el("responsive");
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
|
|
249
|
+
// src/generate-css.ts
|
|
250
|
+
import { compileToCSS } from 'chaincss';
|
|
251
|
+
import * as styles from './styles.chain';
|
|
252
|
+
import fs from 'fs';
|
|
540
253
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
```ts
|
|
544
|
-
import {
|
|
545
|
-
math,
|
|
546
|
-
add,
|
|
547
|
-
fluidType,
|
|
548
|
-
convert
|
|
549
|
-
} from "chaincss";
|
|
550
|
-
|
|
551
|
-
add("10px", "2rem");
|
|
552
|
-
|
|
553
|
-
fluidType({
|
|
554
|
-
minSize: 14,
|
|
555
|
-
maxSize: 20
|
|
556
|
-
});
|
|
254
|
+
let css = '*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}\n';
|
|
255
|
+
css += 'body{font-family:system-ui,sans-serif}\n\n';
|
|
557
256
|
|
|
558
|
-
|
|
257
|
+
for (const [_, obj] of Object.entries(styles)) {
|
|
258
|
+
const sel = (obj as any).selectors?.[0];
|
|
259
|
+
if (sel) css += compileToCSS(obj as any, { scopeSelector: sel }) + '\n\n';
|
|
260
|
+
}
|
|
261
|
+
fs.writeFileSync('public/chaincss.css', css);
|
|
559
262
|
```
|
|
560
263
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
import {
|
|
567
|
-
createTokens,
|
|
568
|
-
createThemeContract,
|
|
569
|
-
createTheme
|
|
570
|
-
} from "chaincss";
|
|
571
|
-
|
|
572
|
-
const tokens = createTokens({
|
|
573
|
-
colors: {
|
|
574
|
-
primary: "#6366f1",
|
|
575
|
-
secondary: "#10b981"
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"scripts": {
|
|
267
|
+
"dev": "npm run css && vite",
|
|
268
|
+
"css": "npx tsx src/generate-css.ts"
|
|
576
269
|
}
|
|
577
|
-
}
|
|
270
|
+
}
|
|
578
271
|
```
|
|
579
272
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
# Recipe System
|
|
273
|
+
Debug Mode
|
|
583
274
|
|
|
584
275
|
```ts
|
|
585
|
-
|
|
276
|
+
const debugChain = chain({ debug: true })
|
|
277
|
+
.bg('red')
|
|
278
|
+
.color(() => themeColor)
|
|
279
|
+
.padding(16);
|
|
586
280
|
|
|
587
|
-
|
|
588
|
-
base: {
|
|
589
|
-
selectors: ["btn"],
|
|
590
|
-
display: "inline-flex",
|
|
591
|
-
borderRadius: "8px"
|
|
592
|
-
}
|
|
593
|
-
});
|
|
281
|
+
console.log(debugChain.explain().visualization);
|
|
594
282
|
```
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
.zoomIn()
|
|
605
|
-
.bounce()
|
|
606
|
-
.pulse()
|
|
607
|
-
.$el("animated");
|
|
283
|
+
Output:
|
|
284
|
+
```text
|
|
285
|
+
ChainCSS Style Explanation
|
|
286
|
+
--------------------------
|
|
287
|
+
bg -> red (static)
|
|
288
|
+
color -> <fn> (dynamic)
|
|
289
|
+
padding -> 16 (static)
|
|
290
|
+
--------------------------
|
|
291
|
+
Static: 2 | Dynamic: 1
|
|
608
292
|
```
|
|
609
293
|
|
|
294
|
+
Class Names
|
|
295
|
+
`chain().$el('button')` produces the selector `.chain-button`. To use it in JSX:
|
|
610
296
|
|
|
297
|
+
```tsx
|
|
298
|
+
// Option 1: Extract manually
|
|
299
|
+
<button className={btn.selectors[0].replace('.', '')}>Click</button>
|
|
611
300
|
|
|
612
|
-
|
|
301
|
+
// Option 2: Helper function
|
|
302
|
+
const cls = (c: any) => c.selectors?.[0]?.replace('.', '') || '';
|
|
303
|
+
<button className={cls(btn)}>Click</button>
|
|
613
304
|
|
|
614
|
-
|
|
615
|
-
import
|
|
616
|
-
|
|
617
|
-
correct("display", "flexbox");
|
|
618
|
-
correct("position", "abs");
|
|
619
|
-
|
|
620
|
-
heal(
|
|
621
|
-
{
|
|
622
|
-
display: "flexbox",
|
|
623
|
-
position: "abs"
|
|
624
|
-
},
|
|
625
|
-
"smart"
|
|
626
|
-
);
|
|
305
|
+
// Option 3: Wrapper file (styles.ts)
|
|
306
|
+
import * as S from './styles.chain';
|
|
307
|
+
export const btn = S.btn.selectors[0].replace('.', '');
|
|
627
308
|
```
|
|
628
309
|
|
|
310
|
+
Framework Integration
|
|
311
|
+
ChainCSS is framework-agnostic. `$el()` returns a plain object:
|
|
629
312
|
|
|
313
|
+
```tsx
|
|
314
|
+
// React
|
|
315
|
+
<div style={styles}>...</div>
|
|
630
316
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
```bash
|
|
634
|
-
chaincss init
|
|
635
|
-
chaincss build
|
|
636
|
-
chaincss watch
|
|
637
|
-
chaincss cache clear
|
|
638
|
-
chaincss optimize --report
|
|
639
|
-
chaincss doctor
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
# Performance
|
|
645
|
-
|
|
646
|
-
- Zero runtime for static styles
|
|
647
|
-
- Atomic CSS extraction
|
|
648
|
-
- Smart static/dynamic splitting
|
|
649
|
-
- Cross-file dead code elimination
|
|
650
|
-
- Multi-pass optimization pipeline
|
|
317
|
+
// Vue
|
|
318
|
+
<div :style="styles">...</div>
|
|
651
319
|
|
|
320
|
+
// Svelte
|
|
321
|
+
<div style={styles}>...</div>
|
|
652
322
|
|
|
323
|
+
// Plain HTML (with CSS file)
|
|
324
|
+
<div class="chain-button">...</div>
|
|
325
|
+
```
|
|
653
326
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
```bash
|
|
657
|
-
git clone https://github.com/melcanz08/chaincss.git
|
|
658
|
-
|
|
659
|
-
cd chaincss
|
|
660
|
-
|
|
661
|
-
npm install
|
|
327
|
+
API Reference
|
|
328
|
+
Main Exports
|
|
662
329
|
|
|
663
|
-
|
|
664
|
-
|
|
330
|
+
| Export | Description |
|
|
331
|
+
| :--- | :--- |
|
|
332
|
+
| chain(options?) | Create a style chain |
|
|
333
|
+
| compileToCSS(obj, opts?) | Compile style object to CSS string |
|
|
334
|
+
| partitionForBuild(obj, opts?) | Split static/dynamic, return { css, dynamicValues } |
|
|
335
|
+
| classifyValue(value) | Returns 'static' or 'dynamic' |
|
|
336
|
+
| partitionStyles(obj) | Split object into { static, dynamic } |
|
|
337
|
+
| compileToCSS | Generate CSS from style object |
|
|
665
338
|
|
|
339
|
+
Value Classification
|
|
666
340
|
|
|
341
|
+
| Value type | Classification | Behavior |
|
|
342
|
+
| :--- | :--- | :--- |
|
|
343
|
+
| '#6366f1' (string) | static | Compiled to CSS |
|
|
344
|
+
| 16 (number) | static | Compiled to CSS (px added if needed) |
|
|
345
|
+
| () => themeColor (function) | dynamic | Stays in JS, resolved at runtime |
|
|
346
|
+
| 'theme.primary' (token ref) | dynamic | Resolved at runtime |
|
|
667
347
|
|
|
668
|
-
|
|
348
|
+
Migration from v2.3
|
|
669
349
|
|
|
670
|
-
|
|
350
|
+
| Old API | New API |
|
|
351
|
+
| :--- | :--- |
|
|
352
|
+
| createChain() | chain() |
|
|
353
|
+
| smartChain() | chain() |
|
|
354
|
+
| buildChain() | chain() — static values auto-detected |
|
|
355
|
+
| runtimeChain() | chain() — dynamic values auto-detected |
|
|
356
|
+
| btt.compile() | compileToCSS() |
|
|
357
|
+
| btt.run() | compileToCSS() |
|
|
358
|
+
| enableDebug() | chain({ debug: true }) |
|
|
671
359
|
|
|
672
|
-
|
|
673
|
-
|
|
360
|
+
License
|
|
361
|
+
MIT
|
|
674
362
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
</a>
|
|
678
|
-
</p>
|
|
363
|
+
Author
|
|
364
|
+
Rommel Caneos
|
package/dist/plugins/vite.d.ts
CHANGED
package/dist/plugins/vite.js
CHANGED
|
@@ -19,6 +19,7 @@ function chaincssPlugin(options = {}) {
|
|
|
19
19
|
if (file === cssPath) {
|
|
20
20
|
try {
|
|
21
21
|
cssContent = fs.readFileSync(cssPath, "utf8");
|
|
22
|
+
log("CSS updated");
|
|
22
23
|
} catch {
|
|
23
24
|
}
|
|
24
25
|
server.ws.send({ type: "full-reload" });
|
|
@@ -26,7 +27,9 @@ function chaincssPlugin(options = {}) {
|
|
|
26
27
|
});
|
|
27
28
|
try {
|
|
28
29
|
cssContent = fs.readFileSync(cssPath, "utf8");
|
|
30
|
+
log(`Serving ${cssContent.length} bytes`);
|
|
29
31
|
} catch {
|
|
32
|
+
log("No CSS file yet");
|
|
30
33
|
}
|
|
31
34
|
server.middlewares.use("/__chaincss.css", (_req, res) => {
|
|
32
35
|
res.setHeader("Content-Type", "text/css");
|
package/package.json
CHANGED
package/src/plugins/vite.ts
CHANGED
|
@@ -2,11 +2,7 @@ import { Plugin } from 'vite';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
verbose?: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export default function chaincssPlugin(options: ChainCSSPluginOptions = {}): Plugin {
|
|
5
|
+
export default function chaincssPlugin(options: { verbose?: boolean } = {}): Plugin {
|
|
10
6
|
let cssContent = '';
|
|
11
7
|
let cssPath = '';
|
|
12
8
|
|
|
@@ -26,12 +22,11 @@ export default function chaincssPlugin(options: ChainCSSPluginOptions = {}): Plu
|
|
|
26
22
|
server.watcher.add(cssPath);
|
|
27
23
|
server.watcher.on('change', (file: string) => {
|
|
28
24
|
if (file === cssPath) {
|
|
29
|
-
try { cssContent = fs.readFileSync(cssPath, 'utf8'); } catch {}
|
|
25
|
+
try { cssContent = fs.readFileSync(cssPath, 'utf8'); log('CSS updated'); } catch {}
|
|
30
26
|
server.ws.send({ type: 'full-reload' });
|
|
31
27
|
}
|
|
32
28
|
});
|
|
33
|
-
|
|
34
|
-
try { cssContent = fs.readFileSync(cssPath, 'utf8'); } catch {}
|
|
29
|
+
try { cssContent = fs.readFileSync(cssPath, 'utf8'); log(`Serving ${cssContent.length} bytes`); } catch { log('No CSS file yet'); }
|
|
35
30
|
|
|
36
31
|
server.middlewares.use('/__chaincss.css', (_req, res) => {
|
|
37
32
|
res.setHeader('Content-Type', 'text/css');
|