@aihu/css-engine 0.2.5 → 0.4.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/README.md +27 -22
- package/crates/aihu-css-core/src/apply.rs +314 -0
- package/crates/aihu-css-core/src/bin/main.rs +8 -7
- package/crates/aihu-css-core/src/cache.rs +8 -5
- package/crates/aihu-css-core/src/emit.rs +195 -36
- package/crates/aihu-css-core/src/lib.rs +15 -2
- package/crates/aihu-css-core/src/palette.rs +301 -0
- package/crates/aihu-css-core/src/style_parser.rs +587 -0
- package/crates/aihu-css-core/src/theme.rs +14 -0
- package/crates/aihu-css-core/src/tokens.rs +1196 -29
- package/crates/aihu-css-core/src/variants.rs +251 -3
- package/crates/aihu-css-core/tests/apply.rs +203 -0
- package/crates/aihu-css-core/tests/apply_regression.rs +150 -0
- package/crates/aihu-css-core/tests/binary_error.rs +61 -0
- package/crates/aihu-css-core/tests/cache.rs +8 -8
- package/crates/aihu-css-core/tests/emit.rs +284 -17
- package/crates/aihu-css-core/tests/parity.rs +274 -0
- package/crates/aihu-css-core/tests/progressive_snapshot.rs +8 -8
- package/crates/aihu-css-core/tests/scoped_snapshot.rs +80 -8
- package/crates/aihu-css-core/tests/snapshots/apply__apply_inside_nested_rule.snap +11 -0
- package/crates/aihu-css-core/tests/snapshots/apply__arbitrary_value_utility_in_apply.snap +8 -0
- package/crates/aihu-css-core/tests/snapshots/apply__arbitrary_value_variant_in_apply.snap +10 -0
- package/crates/aihu-css-core/tests/snapshots/apply__base_utility_inlines_declarations.snap +9 -0
- package/crates/aihu-css-core/tests/snapshots/apply__dark_variant_cascade_in_apply.snap +10 -0
- package/crates/aihu-css-core/tests/snapshots/apply__data_attribute_variant.snap +10 -0
- package/crates/aihu-css-core/tests/snapshots/apply__multi_token_apply.snap +11 -0
- package/crates/aihu-css-core/tests/snapshots/apply__multiple_apply_directives_per_rule.snap +12 -0
- package/crates/aihu-css-core/tests/snapshots/apply__responsive_variant_wraps_media.snap +12 -0
- package/crates/aihu-css-core/tests/snapshots/apply__single_variant_lifts_to_nested_rule.snap +10 -0
- package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__anchor_snapshot.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__popover_snapshot.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__text_balance_snapshot.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/progressive_snapshot__view_transition_snapshot.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_animate_spin_hoists_keyframes.snap +25 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_divide_y_nested_rule.snap +24 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_output_for_sfc.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_space_y_nested_rule.snap +24 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_transition_and_transform.snap +26 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_with_authored_style_block.snap +6 -2
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__scoped_with_global_style_block.snap +5 -2
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__standard_variants.snap +1 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__theme_default_vs_override.snap +2 -0
- package/crates/aihu-css-core/tests/snapshots/scoped_snapshot__wc_native_variants.snap +1 -0
- package/crates/aihu-css-core/tests/style_parser.rs +257 -0
- package/crates/aihu-css-core/tests/tokens.rs +526 -7
- package/dist/index.d.ts +0 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -18
- package/dist/index.js.map +1 -1
- package/dist/runtime/cn.js +13 -0
- package/dist/runtime/cn.js.map +1 -1
- package/package.json +6 -6
|
@@ -22,7 +22,9 @@ fn category_colors() {
|
|
|
22
22
|
|
|
23
23
|
#[test]
|
|
24
24
|
fn category_spacing() {
|
|
25
|
-
insta::assert_snapshot!(css(&[
|
|
25
|
+
insta::assert_snapshot!(css(&[
|
|
26
|
+
"p-4", "px-2", "py-8", "m-0", "mt-1", "gap-2", "p-0.5"
|
|
27
|
+
]));
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
#[test]
|
|
@@ -54,12 +56,7 @@ fn category_typography() {
|
|
|
54
56
|
|
|
55
57
|
#[test]
|
|
56
58
|
fn category_borders() {
|
|
57
|
-
insta::assert_snapshot!(css(&[
|
|
58
|
-
"border",
|
|
59
|
-
"rounded",
|
|
60
|
-
"rounded-lg",
|
|
61
|
-
"rounded-full",
|
|
62
|
-
]));
|
|
59
|
+
insta::assert_snapshot!(css(&["border", "rounded", "rounded-lg", "rounded-full",]));
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
#[test]
|
|
@@ -67,6 +64,17 @@ fn category_effects() {
|
|
|
67
64
|
insta::assert_snapshot!(css(&["shadow", "shadow-lg", "shadow-none", "opacity-50"]));
|
|
68
65
|
}
|
|
69
66
|
|
|
67
|
+
#[test]
|
|
68
|
+
fn relational_marker_classes() {
|
|
69
|
+
// `group` / `peer` are marker utilities: they emit an empty-body rule (no
|
|
70
|
+
// declarations) so the class survives into the sheet for `group-*:` /
|
|
71
|
+
// `peer-*:` relational selectors to target. (Variant emission itself is
|
|
72
|
+
// covered in tests/emit.rs, which uses the scoped pipeline.)
|
|
73
|
+
let out = css(&["group", "peer"]);
|
|
74
|
+
assert!(out.contains(".group { }"), "bare group marker: {out}");
|
|
75
|
+
assert!(out.contains(".peer { }"), "bare peer marker: {out}");
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
#[test]
|
|
71
79
|
fn arbitrary_values() {
|
|
72
80
|
insta::assert_snapshot!(css(&[
|
|
@@ -77,3 +85,514 @@ fn arbitrary_values() {
|
|
|
77
85
|
"leading-[1.4]",
|
|
78
86
|
]));
|
|
79
87
|
}
|
|
88
|
+
|
|
89
|
+
// --- New utility families (Round 1: tailwind-support) ---------------------
|
|
90
|
+
//
|
|
91
|
+
// These use exact-string assertions (not snapshots) so the emitted CSS is
|
|
92
|
+
// pinned per-class. The expected declarations mirror Tailwind v4 defaults.
|
|
93
|
+
|
|
94
|
+
#[test]
|
|
95
|
+
fn space_x_emits_nested_sibling_margin() {
|
|
96
|
+
assert_eq!(
|
|
97
|
+
css(&["space-x-4"]),
|
|
98
|
+
".space-x-4 { & > * + * { margin-inline-start: 1rem; } }\n"
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[test]
|
|
103
|
+
fn space_y_emits_nested_sibling_margin() {
|
|
104
|
+
assert_eq!(
|
|
105
|
+
css(&["space-y-2"]),
|
|
106
|
+
".space-y-2 { & > * + * { margin-block-start: 0.5rem; } }\n"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn mx_auto_emits_margin_inline_auto() {
|
|
112
|
+
assert_eq!(css(&["mx-auto"]), ".mx-auto { margin-inline: auto; }\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#[test]
|
|
116
|
+
fn max_w_named_scale() {
|
|
117
|
+
assert_eq!(css(&["max-w-7xl"]), ".max-w-7xl { max-width: 80rem; }\n");
|
|
118
|
+
assert_eq!(css(&["max-w-prose"]), ".max-w-prose { max-width: 65ch; }\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[test]
|
|
122
|
+
fn grid_cols_repeat() {
|
|
123
|
+
assert_eq!(
|
|
124
|
+
css(&["grid-cols-3"]),
|
|
125
|
+
".grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }\n"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
fn col_span_n() {
|
|
131
|
+
assert_eq!(
|
|
132
|
+
css(&["col-span-2"]),
|
|
133
|
+
".col-span-2 { grid-column: span 2 / span 2; }\n"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[test]
|
|
138
|
+
fn row_span_full_keyword() {
|
|
139
|
+
assert_eq!(
|
|
140
|
+
css(&["row-span-full"]),
|
|
141
|
+
".row-span-full { grid-row: 1 / -1; }\n"
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[test]
|
|
146
|
+
fn border_n_width() {
|
|
147
|
+
assert_eq!(css(&["border-2"]), ".border-2 { border-width: 2px; }\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[test]
|
|
151
|
+
fn border_directional_n_width() {
|
|
152
|
+
assert_eq!(
|
|
153
|
+
css(&["border-t-4"]),
|
|
154
|
+
".border-t-4 { border-top-width: 4px; }\n"
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[test]
|
|
159
|
+
fn z_auto_keyword() {
|
|
160
|
+
assert_eq!(css(&["z-auto"]), ".z-auto { z-index: auto; }\n");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// --- Round 2: divide-x / divide-y sibling borders -------------------------
|
|
164
|
+
//
|
|
165
|
+
// Reuses the proven `space-*` nested `& > * + *` recipe. Exact-string
|
|
166
|
+
// assertions pin every family: bare (1px default), numeric widths, reverse.
|
|
167
|
+
|
|
168
|
+
#[test]
|
|
169
|
+
fn divide_x_bare_defaults_to_1px() {
|
|
170
|
+
assert_eq!(
|
|
171
|
+
css(&["divide-x"]),
|
|
172
|
+
".divide-x { & > * + * { border-inline-width: 1px; } }\n"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// --- New utility families (Round 2: tailwind-support — named scales) -------
|
|
177
|
+
//
|
|
178
|
+
// Position scale (top/right/bottom/left/inset/inset-x/inset-y) on the spacing
|
|
179
|
+
// scale + `auto` + negative forms; named leading-* / numeric leading-<n>;
|
|
180
|
+
// named tracking-*. Exact-string assertions pin the emitted declarations.
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn position_top_scale() {
|
|
184
|
+
assert_eq!(css(&["top-4"]), ".top-4 { top: 1rem; }\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#[test]
|
|
188
|
+
fn position_right_scale() {
|
|
189
|
+
assert_eq!(css(&["right-4"]), ".right-4 { right: 1rem; }\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#[test]
|
|
193
|
+
fn position_bottom_left_scale() {
|
|
194
|
+
assert_eq!(css(&["bottom-2"]), ".bottom-2 { bottom: 0.5rem; }\n");
|
|
195
|
+
assert_eq!(css(&["left-8"]), ".left-8 { left: 2rem; }\n");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[test]
|
|
199
|
+
fn position_inset_all_sides() {
|
|
200
|
+
assert_eq!(css(&["inset-0"]), ".inset-0 { inset: 0; }\n");
|
|
201
|
+
assert_eq!(css(&["inset-4"]), ".inset-4 { inset: 1rem; }\n");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[test]
|
|
205
|
+
fn position_inset_logical_axes() {
|
|
206
|
+
assert_eq!(css(&["inset-x-2"]), ".inset-x-2 { inset-inline: 0.5rem; }\n");
|
|
207
|
+
assert_eq!(css(&["inset-y-4"]), ".inset-y-4 { inset-block: 1rem; }\n");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#[test]
|
|
211
|
+
fn position_auto() {
|
|
212
|
+
assert_eq!(css(&["top-auto"]), ".top-auto { top: auto; }\n");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[test]
|
|
216
|
+
fn position_negative() {
|
|
217
|
+
assert_eq!(css(&["-left-2"]), ".-left-2 { left: -0.5rem; }\n");
|
|
218
|
+
assert_eq!(css(&["-top-4"]), ".-top-4 { top: -1rem; }\n");
|
|
219
|
+
assert_eq!(css(&["-inset-x-2"]), ".-inset-x-2 { inset-inline: -0.5rem; }\n");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[test]
|
|
223
|
+
fn leading_named_scale() {
|
|
224
|
+
assert_eq!(css(&["leading-none"]), ".leading-none { line-height: 1; }\n");
|
|
225
|
+
assert_eq!(css(&["leading-tight"]), ".leading-tight { line-height: 1.25; }\n");
|
|
226
|
+
assert_eq!(css(&["leading-snug"]), ".leading-snug { line-height: 1.375; }\n");
|
|
227
|
+
assert_eq!(css(&["leading-normal"]), ".leading-normal { line-height: 1.5; }\n");
|
|
228
|
+
assert_eq!(
|
|
229
|
+
css(&["leading-relaxed"]),
|
|
230
|
+
".leading-relaxed { line-height: 1.625; }\n"
|
|
231
|
+
);
|
|
232
|
+
assert_eq!(css(&["leading-loose"]), ".leading-loose { line-height: 2; }\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#[test]
|
|
236
|
+
fn leading_numeric_step() {
|
|
237
|
+
// Numeric leading-<n> maps to the spacing scale (Tailwind v4).
|
|
238
|
+
assert_eq!(css(&["leading-6"]), ".leading-6 { line-height: 1.5rem; }\n");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[test]
|
|
242
|
+
fn tracking_named_scale() {
|
|
243
|
+
assert_eq!(
|
|
244
|
+
css(&["tracking-tighter"]),
|
|
245
|
+
".tracking-tighter { letter-spacing: -0.05em; }\n"
|
|
246
|
+
);
|
|
247
|
+
assert_eq!(
|
|
248
|
+
css(&["tracking-tight"]),
|
|
249
|
+
".tracking-tight { letter-spacing: -0.025em; }\n"
|
|
250
|
+
);
|
|
251
|
+
assert_eq!(
|
|
252
|
+
css(&["tracking-normal"]),
|
|
253
|
+
".tracking-normal { letter-spacing: 0em; }\n"
|
|
254
|
+
);
|
|
255
|
+
assert_eq!(
|
|
256
|
+
css(&["tracking-wide"]),
|
|
257
|
+
".tracking-wide { letter-spacing: 0.025em; }\n"
|
|
258
|
+
);
|
|
259
|
+
assert_eq!(
|
|
260
|
+
css(&["tracking-wider"]),
|
|
261
|
+
".tracking-wider { letter-spacing: 0.05em; }\n"
|
|
262
|
+
);
|
|
263
|
+
assert_eq!(
|
|
264
|
+
css(&["tracking-widest"]),
|
|
265
|
+
".tracking-widest { letter-spacing: 0.1em; }\n"
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --- New utility families (Round 2: ring widths + ring-offset) -------------
|
|
270
|
+
//
|
|
271
|
+
// `ring-{n}` emits the Tailwind v4 box-shadow ring composed from `--tw-ring-*`
|
|
272
|
+
// custom properties; `ring-offset-{n}` sets `--tw-ring-offset-width`;
|
|
273
|
+
// `ring-inset` flips the inset slot; bare `ring` is the 3px default. CRITICAL
|
|
274
|
+
// regression guard: `ring-<color>` must STILL route to `--tw-ring-color`.
|
|
275
|
+
|
|
276
|
+
#[test]
|
|
277
|
+
fn ring_n_emits_box_shadow_ring() {
|
|
278
|
+
assert_eq!(
|
|
279
|
+
css(&["ring-2"]),
|
|
280
|
+
".ring-2 { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 \
|
|
281
|
+
var(--tw-ring-offset-width) var(--tw-ring-offset-color); \
|
|
282
|
+
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) \
|
|
283
|
+
var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), \
|
|
284
|
+
var(--tw-shadow, 0 0 #0000); }\n"
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// --- Motion family (Round 2: tailwind-support `motion` track) -------------
|
|
289
|
+
|
|
290
|
+
#[test]
|
|
291
|
+
fn transform_keyword_and_none() {
|
|
292
|
+
assert_eq!(
|
|
293
|
+
css(&["transform"]),
|
|
294
|
+
".transform { transform: translate(0, 0) rotate(0) skewX(0) skewY(0) scaleX(1) scaleY(1); }\n"
|
|
295
|
+
);
|
|
296
|
+
assert_eq!(
|
|
297
|
+
css(&["transform-none"]),
|
|
298
|
+
".transform-none { transform: none; }\n"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// --- Round 2: container-query marker utilities (aria-data-container) -------
|
|
303
|
+
//
|
|
304
|
+
// The `@container` marker and its named `@container/<name>` form are base
|
|
305
|
+
// utilities (the `@sm:`/`@md:`/`@lg:` query variants live in variants.rs /
|
|
306
|
+
// emit.rs and are exercised in tests/emit.rs).
|
|
307
|
+
|
|
308
|
+
#[test]
|
|
309
|
+
fn container_marker_emits_inline_size() {
|
|
310
|
+
assert_eq!(
|
|
311
|
+
css(&["@container"]),
|
|
312
|
+
".@container { container-type: inline-size; }\n"
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[test]
|
|
317
|
+
fn divide_y_bare_defaults_to_1px() {
|
|
318
|
+
assert_eq!(
|
|
319
|
+
css(&["divide-y"]),
|
|
320
|
+
".divide-y { & > * + * { border-block-width: 1px; } }\n"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
#[test]
|
|
325
|
+
fn translate_x_uses_spacing_scale() {
|
|
326
|
+
assert_eq!(
|
|
327
|
+
css(&["translate-x-2"]),
|
|
328
|
+
".translate-x-2 { transform: translateX(0.5rem); }\n"
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#[test]
|
|
333
|
+
fn ring_default_is_3px() {
|
|
334
|
+
// The bare `ring` keyword is the 3px default and must match BEFORE the
|
|
335
|
+
// color path (it never collides with `ring-<color>`).
|
|
336
|
+
assert!(css(&["ring"]).contains("calc(3px + var(--tw-ring-offset-width))"));
|
|
337
|
+
assert!(css(&["ring"]).starts_with(".ring {"));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[test]
|
|
341
|
+
fn ring_zero_emits_zero_width_ring() {
|
|
342
|
+
assert!(css(&["ring-0"]).contains("calc(0px + var(--tw-ring-offset-width))"));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#[test]
|
|
346
|
+
fn ring_inset_flips_inset_slot() {
|
|
347
|
+
assert_eq!(css(&["ring-inset"]), ".ring-inset { --tw-ring-inset: inset; }\n");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
#[test]
|
|
351
|
+
fn ring_offset_n_sets_offset_width() {
|
|
352
|
+
assert_eq!(
|
|
353
|
+
css(&["ring-offset-2"]),
|
|
354
|
+
".ring-offset-2 { --tw-ring-offset-width: 2px; }\n"
|
|
355
|
+
);
|
|
356
|
+
assert_eq!(
|
|
357
|
+
css(&["ring-offset-8"]),
|
|
358
|
+
".ring-offset-8 { --tw-ring-offset-width: 8px; }\n"
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#[test]
|
|
363
|
+
fn negative_translate_x_emits_negative_value() {
|
|
364
|
+
assert_eq!(
|
|
365
|
+
css(&["-translate-x-2"]),
|
|
366
|
+
".-translate-x-2 { transform: translateX(-0.5rem); }\n"
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#[test]
|
|
371
|
+
fn divide_y_2_emits_border_block_width() {
|
|
372
|
+
assert_eq!(
|
|
373
|
+
css(&["divide-y-2"]),
|
|
374
|
+
".divide-y-2 { & > * + * { border-block-width: 2px; } }\n"
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
#[test]
|
|
379
|
+
fn negative_translate_y_emits_negative_value() {
|
|
380
|
+
assert_eq!(
|
|
381
|
+
css(&["-translate-y-4"]),
|
|
382
|
+
".-translate-y-4 { transform: translateY(-1rem); }\n"
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#[test]
|
|
387
|
+
fn divide_x_4_emits_border_inline_width() {
|
|
388
|
+
assert_eq!(
|
|
389
|
+
css(&["divide-x-4"]),
|
|
390
|
+
".divide-x-4 { & > * + * { border-inline-width: 4px; } }\n"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
#[test]
|
|
395
|
+
fn rotate_emits_degrees() {
|
|
396
|
+
assert_eq!(
|
|
397
|
+
css(&["rotate-45"]),
|
|
398
|
+
".rotate-45 { transform: rotate(45deg); }\n"
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#[test]
|
|
403
|
+
fn divide_x_0_emits_zero_width() {
|
|
404
|
+
assert_eq!(
|
|
405
|
+
css(&["divide-x-0"]),
|
|
406
|
+
".divide-x-0 { & > * + * { border-inline-width: 0; } }\n"
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
#[test]
|
|
411
|
+
fn negative_rotate_emits_negative_degrees() {
|
|
412
|
+
assert_eq!(
|
|
413
|
+
css(&["-rotate-45"]),
|
|
414
|
+
".-rotate-45 { transform: rotate(-45deg); }\n"
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
#[test]
|
|
419
|
+
fn divide_x_8_emits_border_inline_width() {
|
|
420
|
+
assert_eq!(
|
|
421
|
+
css(&["divide-x-8"]),
|
|
422
|
+
".divide-x-8 { & > * + * { border-inline-width: 8px; } }\n"
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#[test]
|
|
427
|
+
fn scale_maps_percent_to_factor() {
|
|
428
|
+
assert_eq!(
|
|
429
|
+
css(&["scale-105"]),
|
|
430
|
+
".scale-105 { transform: scale(1.05); }\n"
|
|
431
|
+
);
|
|
432
|
+
assert_eq!(
|
|
433
|
+
css(&["scale-x-50"]),
|
|
434
|
+
".scale-x-50 { transform: scaleX(0.5); }\n"
|
|
435
|
+
);
|
|
436
|
+
assert_eq!(
|
|
437
|
+
css(&["scale-y-100"]),
|
|
438
|
+
".scale-y-100 { transform: scaleY(1); }\n"
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[test]
|
|
443
|
+
fn divide_reverse_sets_custom_property() {
|
|
444
|
+
assert_eq!(
|
|
445
|
+
css(&["divide-x-reverse"]),
|
|
446
|
+
".divide-x-reverse { & > * + * { --tw-divide-x-reverse: 1; } }\n"
|
|
447
|
+
);
|
|
448
|
+
assert_eq!(
|
|
449
|
+
css(&["divide-y-reverse"]),
|
|
450
|
+
".divide-y-reverse { & > * + * { --tw-divide-y-reverse: 1; } }\n"
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
#[test]
|
|
455
|
+
fn transition_colors_emits_property_duration_timing() {
|
|
456
|
+
assert_eq!(
|
|
457
|
+
css(&["transition-colors"]),
|
|
458
|
+
".transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }\n"
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
#[test]
|
|
463
|
+
fn arbitrary_position_and_leading_still_work() {
|
|
464
|
+
// Regression: arbitrary forms (arbitrary_prop) untouched by the named scale.
|
|
465
|
+
assert_eq!(css(&["top-[1rem]"]), ".top-[1rem] { top: 1rem; }\n");
|
|
466
|
+
assert_eq!(css(&["leading-[2]"]), ".leading-[2] { line-height: 2; }\n");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
#[test]
|
|
470
|
+
fn ring_color_still_routes_to_ring_color_var() {
|
|
471
|
+
// Regression: adding the WIDTH side must NOT break the existing COLOR path.
|
|
472
|
+
assert_eq!(
|
|
473
|
+
css(&["ring-blue-500"]),
|
|
474
|
+
".ring-blue-500 { --tw-ring-color: var(--color-blue-500); }\n"
|
|
475
|
+
);
|
|
476
|
+
// Brand ring token too.
|
|
477
|
+
assert_eq!(
|
|
478
|
+
css(&["ring-primary"]),
|
|
479
|
+
".ring-primary { --tw-ring-color: var(--color-primary); }\n"
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
#[test]
|
|
484
|
+
fn transition_none_and_transform_variants() {
|
|
485
|
+
assert_eq!(
|
|
486
|
+
css(&["transition-none"]),
|
|
487
|
+
".transition-none { transition-property: none; }\n"
|
|
488
|
+
);
|
|
489
|
+
assert!(css(&["transition-transform"]).contains("transition-property: transform;"));
|
|
490
|
+
assert!(css(&["transition-opacity"]).contains("transition-property: opacity;"));
|
|
491
|
+
assert!(css(&["transition-all"]).contains("transition-property: all;"));
|
|
492
|
+
assert!(css(&["transition"]).contains("transition-duration: 150ms;"));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
#[test]
|
|
496
|
+
fn duration_emits_milliseconds() {
|
|
497
|
+
assert_eq!(
|
|
498
|
+
css(&["duration-300"]),
|
|
499
|
+
".duration-300 { transition-duration: 300ms; }\n"
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
#[test]
|
|
504
|
+
fn ease_timing_functions() {
|
|
505
|
+
assert_eq!(
|
|
506
|
+
css(&["ease-linear"]),
|
|
507
|
+
".ease-linear { transition-timing-function: linear; }\n"
|
|
508
|
+
);
|
|
509
|
+
assert!(css(&["ease-in"]).contains("cubic-bezier(0.4, 0, 1, 1)"));
|
|
510
|
+
assert!(css(&["ease-out"]).contains("cubic-bezier(0, 0, 0.2, 1)"));
|
|
511
|
+
assert!(css(&["ease-in-out"]).contains("cubic-bezier(0.4, 0, 0.2, 1)"));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
#[test]
|
|
515
|
+
fn animate_spin_emits_animation_and_keyframes() {
|
|
516
|
+
// The animation shorthand AND its hoisted @keyframes (sibling rule).
|
|
517
|
+
assert_eq!(
|
|
518
|
+
css(&["animate-spin"]),
|
|
519
|
+
".animate-spin { animation: spin 1s linear infinite; }\n\
|
|
520
|
+
@keyframes spin { to { transform: rotate(360deg); } }\n"
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
#[test]
|
|
525
|
+
fn animate_none_has_no_keyframes() {
|
|
526
|
+
assert_eq!(
|
|
527
|
+
css(&["animate-none"]),
|
|
528
|
+
".animate-none { animation: none; }\n"
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
#[test]
|
|
533
|
+
fn animate_ping_pulse_bounce_emit_keyframes() {
|
|
534
|
+
assert!(css(&["animate-ping"]).contains("@keyframes ping"));
|
|
535
|
+
assert!(css(&["animate-pulse"]).contains("@keyframes pulse"));
|
|
536
|
+
assert!(css(&["animate-bounce"]).contains("@keyframes bounce"));
|
|
537
|
+
assert!(css(&["animate-bounce"]).contains("animation: bounce 1s infinite;"));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
#[test]
|
|
541
|
+
fn named_container_marker_emits_type_and_name() {
|
|
542
|
+
assert_eq!(
|
|
543
|
+
css(&["@container/sidebar"]),
|
|
544
|
+
".@container/sidebar { container-type: inline-size; container-name: sidebar; }\n"
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// ── Issue #280: dictionary misses, grid arbitrary, opacity modifiers ─────────
|
|
549
|
+
|
|
550
|
+
#[test]
|
|
551
|
+
fn font_family_utilities_emit() {
|
|
552
|
+
assert_eq!(css(&["font-mono"]), ".font-mono { font-family: var(--font-mono); }\n");
|
|
553
|
+
assert_eq!(css(&["font-sans"]), ".font-sans { font-family: var(--font-sans); }\n");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
#[test]
|
|
557
|
+
fn bare_directional_borders_emit_1px() {
|
|
558
|
+
assert_eq!(css(&["border-t"]), ".border-t { border-top-width: 1px; }\n");
|
|
559
|
+
assert_eq!(css(&["border-r"]), ".border-r { border-right-width: 1px; }\n");
|
|
560
|
+
assert_eq!(css(&["border-b"]), ".border-b { border-bottom-width: 1px; }\n");
|
|
561
|
+
assert_eq!(css(&["border-l"]), ".border-l { border-left-width: 1px; }\n");
|
|
562
|
+
assert_eq!(css(&["border-x"]), ".border-x { border-inline-width: 1px; }\n");
|
|
563
|
+
assert_eq!(css(&["border-y"]), ".border-y { border-block-width: 1px; }\n");
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
#[test]
|
|
567
|
+
fn grid_arbitrary_template_columns_emits() {
|
|
568
|
+
assert_eq!(
|
|
569
|
+
css(&["grid-cols-[2fr_1fr_1fr_1.5fr_1.5fr]"]),
|
|
570
|
+
".grid-cols-[2fr_1fr_1fr_1.5fr_1.5fr] { grid-template-columns: 2fr 1fr 1fr 1.5fr 1.5fr; }\n"
|
|
571
|
+
);
|
|
572
|
+
assert_eq!(
|
|
573
|
+
css(&["grid-rows-[auto_1fr]"]),
|
|
574
|
+
".grid-rows-[auto_1fr] { grid-template-rows: auto 1fr; }\n"
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
#[test]
|
|
579
|
+
fn color_opacity_modifier_emits_color_mix() {
|
|
580
|
+
assert_eq!(
|
|
581
|
+
css(&["bg-accent/15"]),
|
|
582
|
+
".bg-accent/15 { background-color: color-mix(in oklab, var(--color-accent) 15%, transparent); }\n"
|
|
583
|
+
);
|
|
584
|
+
assert_eq!(
|
|
585
|
+
css(&["bg-primary/50"]),
|
|
586
|
+
".bg-primary/50 { background-color: color-mix(in oklab, var(--color-primary) 50%, transparent); }\n"
|
|
587
|
+
);
|
|
588
|
+
assert_eq!(
|
|
589
|
+
css(&["text-red-500/30"]),
|
|
590
|
+
".text-red-500/30 { color: color-mix(in oklab, var(--color-red-500) 30%, transparent); }\n"
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
#[test]
|
|
595
|
+
fn sizing_fraction_not_treated_as_opacity() {
|
|
596
|
+
// `w-1/2` is a width fraction, not a color opacity — must stay 50%.
|
|
597
|
+
assert_eq!(css(&["w-1/2"]), ".w-1/2 { width: 50%; }\n");
|
|
598
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,15 +21,6 @@ import { i as defineStylePack, n as StylePackInput, r as TokenMap, t as StylePac
|
|
|
21
21
|
* still rejects a zero-byte placeholder.
|
|
22
22
|
*/
|
|
23
23
|
declare function isUsableExecutable(candidate: string): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Compile a list of utility class names to CSS.
|
|
26
|
-
*
|
|
27
|
-
* Plan 1 bootstrap — supports a hardcoded subset; see crates/aihu-css-core/src/tokens.rs.
|
|
28
|
-
* Plan 2 wires the AST scanner so callers pass `.aihu` SFC ASTs instead of raw class lists.
|
|
29
|
-
*
|
|
30
|
-
* @param classes - utility class names like `['bg-primary', 'p-4']`
|
|
31
|
-
* @returns CSS string with one rule per known class
|
|
32
|
-
*/
|
|
33
24
|
declare function compile(classes: string[]): string;
|
|
34
25
|
/**
|
|
35
26
|
* Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AA8FA;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;AA8FA;;;;;AA2IA;;;;;AAqBA;;;;;;;iBAhKgB,kBAAA,CAAmB,SAAA;AAAA,iBA2InB,OAAA,CAAQ,OAAA;;;;;;;;;;;;;;iBAqBR,UAAA,CAAW,MAAA,UAAgB,EAAA"}
|
package/dist/index.js
CHANGED
|
@@ -114,17 +114,33 @@ function buildMissingBinaryError(descriptor, devCandidates) {
|
|
|
114
114
|
* @param classes - utility class names like `['bg-primary', 'p-4']`
|
|
115
115
|
* @returns CSS string with one rule per known class
|
|
116
116
|
*/
|
|
117
|
+
/**
|
|
118
|
+
* Spawn the native compiler, returning stdout on success. On a non-zero exit
|
|
119
|
+
* (the binary's R-RESULT error path) throw an `Error` carrying the binary's
|
|
120
|
+
* stderr message rather than letting `execFileSync`'s opaque status error
|
|
121
|
+
* surface. stderr is PIPED (not inherited) so the message lands in the thrown
|
|
122
|
+
* error instead of the parent's console.
|
|
123
|
+
*/
|
|
124
|
+
function runBinary(bin, args, input) {
|
|
125
|
+
try {
|
|
126
|
+
return execFileSync(bin, args, {
|
|
127
|
+
input,
|
|
128
|
+
encoding: "utf-8",
|
|
129
|
+
stdio: [
|
|
130
|
+
"pipe",
|
|
131
|
+
"pipe",
|
|
132
|
+
"pipe"
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
const e = err;
|
|
137
|
+
const detail = (typeof e.stderr === "string" ? e.stderr : e.stderr instanceof Buffer ? e.stderr.toString("utf-8") : "").trim() || e.message || "unknown error";
|
|
138
|
+
throw new Error(`[@aihu/css-engine] CSS compile failed: ${detail}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
117
141
|
function compile(classes) {
|
|
118
142
|
if (classes.length === 0) return "";
|
|
119
|
-
return
|
|
120
|
-
input: classes.join("\n"),
|
|
121
|
-
encoding: "utf-8",
|
|
122
|
-
stdio: [
|
|
123
|
-
"pipe",
|
|
124
|
-
"pipe",
|
|
125
|
-
"inherit"
|
|
126
|
-
]
|
|
127
|
-
});
|
|
143
|
+
return runBinary(resolveBinary(), [], classes.join("\n"));
|
|
128
144
|
}
|
|
129
145
|
/**
|
|
130
146
|
* Compile a `.aihu` SFC source string to scoped, shadow-DOM-embedded CSS.
|
|
@@ -141,15 +157,7 @@ function compile(classes) {
|
|
|
141
157
|
*/
|
|
142
158
|
function compileSfc(source, id) {
|
|
143
159
|
const ast = compileToAst(source, id);
|
|
144
|
-
return
|
|
145
|
-
input: JSON.stringify(ast),
|
|
146
|
-
encoding: "utf-8",
|
|
147
|
-
stdio: [
|
|
148
|
-
"pipe",
|
|
149
|
-
"pipe",
|
|
150
|
-
"inherit"
|
|
151
|
-
]
|
|
152
|
-
});
|
|
160
|
+
return runBinary(resolveBinary(), ["--ast-json"], JSON.stringify(ast));
|
|
153
161
|
}
|
|
154
162
|
//#endregion
|
|
155
163
|
export { compile, compileSfc, defineStylePack, isUsableExecutable };
|