1use std::collections::HashMap;
5
6pub struct TailwindResolver;
7
8impl TailwindResolver {
9 pub fn new() -> Self { Self }
10
11 pub fn resolve(&self, class: &str) -> HashMap<String, String> {
12 let mut out = HashMap::new();
13 if let Some(mapped) = static_map(class) {
14 for (k, v) in mapped { out.insert(k.to_string(), v.to_string()); }
15 return out;
16 }
17 if let Some(mapped) = resolve_negative(class) {
18 for (k, v) in mapped { out.insert(k.to_string(), v.to_string()); }
19 return out;
20 }
21 if let Some(pairs) = parse_arbitrary(class) {
22 for (k, v) in pairs { out.insert(k, v); }
23 return out;
24 }
25 out
26 }
27
28 pub fn resolve_many(&self, class_name: &str) -> HashMap<String, String> {
29 let mut merged = HashMap::new();
30 for cls in class_name.split_whitespace() {
31 for (k, v) in self.resolve(cls) {
32 merged.insert(k, v);
33 }
34 }
35 merged
36 }
37}
38
39fn static_map(class: &str) -> Option<Vec<(&'static str, &'static str)>> {
40 Some(match class {
41 "absolute" => vec![("position", "absolute")],
42 "bg-black" => vec![("background-color", "#000000")],
43 "bg-blue-500" => vec![("background-color", "#3b82f6")],
44 "bg-blue-600" => vec![("background-color", "#2563eb")],
45 "bg-gray-100" => vec![("background-color", "#f3f4f6")],
46 "bg-gray-200" => vec![("background-color", "#e5e7eb")],
47 "bg-gray-300" => vec![("background-color", "#d1d5db")],
48 "bg-gray-400" => vec![("background-color", "#9ca3af")],
49 "bg-gray-50" => vec![("background-color", "#f9fafb")],
50 "bg-gray-500" => vec![("background-color", "#6b7280")],
51 "bg-gray-600" => vec![("background-color", "#4b5563")],
52 "bg-gray-700" => vec![("background-color", "#374151")],
53 "bg-gray-800" => vec![("background-color", "#1f2937")],
54 "bg-gray-900" => vec![("background-color", "#111827")],
55 "bg-green-500" => vec![("background-color", "#22c55e")],
56 "bg-indigo-500" => vec![("background-color", "#6366f1")],
57 "bg-indigo-600" => vec![("background-color", "#4f46e5")],
58 "bg-pink-500" => vec![("background-color", "#ec4899")],
59 "bg-purple-500" => vec![("background-color", "#a855f7")],
60 "bg-purple-600" => vec![("background-color", "#9333ea")],
61 "bg-red-500" => vec![("background-color", "#ef4444")],
62 "bg-transparent" => vec![("background-color", "transparent")],
63 "bg-white" => vec![("background-color", "#ffffff")],
64 "bg-yellow-500" => vec![("background-color", "#eab308")],
65 "block" => vec![("display", "block")],
66 "cursor-default" => vec![("cursor", "default")],
67 "cursor-not-allowed" => vec![("cursor", "not-allowed")],
68 "cursor-pointer" => vec![("cursor", "pointer")],
69 "fixed" => vec![("position", "fixed")],
70 "flex" => vec![("display", "flex")],
71 "flex-1" => vec![("flex-basis", "0%"), ("flex-grow", "1"), ("flex-shrink", "1")],
72 "flex-auto" => vec![("flex-basis", "auto"), ("flex-grow", "1"), ("flex-shrink", "1")],
73 "flex-col" => vec![("flex-direction", "column")],
74 "flex-col-reverse" => vec![("flex-direction", "column-reverse")],
75 "flex-none" => vec![("flex-basis", "auto"), ("flex-grow", "0"), ("flex-shrink", "0")],
76 "flex-nowrap" => vec![("flex-wrap", "nowrap")],
77 "flex-row" => vec![("flex-direction", "row")],
78 "flex-row-reverse" => vec![("flex-direction", "row-reverse")],
79 "flex-wrap" => vec![("flex-wrap", "wrap")],
80 "font-bold" => vec![("font-weight", "700")],
81 "font-extrabold" => vec![("font-weight", "800")],
82 "font-light" => vec![("font-weight", "300")],
83 "font-medium" => vec![("font-weight", "500")],
84 "font-normal" => vec![("font-weight", "400")],
85 "font-semibold" => vec![("font-weight", "600")],
86 "font-thin" => vec![("font-weight", "100")],
87 "gap-0" => vec![("gap", "0px")],
88 "gap-1" => vec![("gap", "4px")],
89 "gap-2" => vec![("gap", "8px")],
90 "gap-3" => vec![("gap", "12px")],
91 "gap-4" => vec![("gap", "16px")],
92 "gap-6" => vec![("gap", "24px")],
93 "gap-8" => vec![("gap", "32px")],
94 "h-0" => vec![("height", "0px")],
95 "h-1" => vec![("height", "4px")],
96 "h-10" => vec![("height", "40px")],
97 "h-12" => vec![("height", "48px")],
98 "h-16" => vec![("height", "64px")],
99 "h-2" => vec![("height", "8px")],
100 "h-20" => vec![("height", "80px")],
101 "h-4" => vec![("height", "16px")],
102 "h-6" => vec![("height", "24px")],
103 "h-8" => vec![("height", "32px")],
104 "h-auto" => vec![("height", "auto")],
105 "h-full" => vec![("height", "100%")],
106 "h-screen" => vec![("height", "100vh")],
107 "hidden" => vec![("display", "none")],
108 "inline" => vec![("display", "inline")],
109 "inline-block" => vec![("display", "inline-block")],
110 "inline-flex" => vec![("display", "inline-flex")],
111 "items-center" => vec![("align-items", "center")],
112 "items-end" => vec![("align-items", "flex-end")],
113 "items-start" => vec![("align-items", "flex-start")],
114 "items-stretch" => vec![("align-items", "stretch")],
115 "justify-around" => vec![("justify-content", "space-around")],
116 "justify-between" => vec![("justify-content", "space-between")],
117 "justify-center" => vec![("justify-content", "center")],
118 "justify-end" => vec![("justify-content", "flex-end")],
119 "justify-start" => vec![("justify-content", "flex-start")],
120 "m-0" => vec![("margin", "0px")],
121 "m-1" => vec![("margin", "4px")],
122 "m-2" => vec![("margin", "8px")],
123 "m-4" => vec![("margin", "16px")],
124 "m-6" => vec![("margin", "24px")],
125 "m-8" => vec![("margin", "32px")],
126 "m-auto" => vec![("margin", "auto")],
127 "mb-2" => vec![("margin-bottom", "8px")],
128 "mb-4" => vec![("margin-bottom", "16px")],
129 "mb-8" => vec![("margin-bottom", "32px")],
130 "min-h-0" => vec![("min-height", "0px")],
131 "min-w-0" => vec![("min-width", "0px")],
132 "ml-2" => vec![("margin-left", "8px")],
133 "ml-4" => vec![("margin-left", "16px")],
134 "mr-2" => vec![("margin-right", "8px")],
135 "mr-4" => vec![("margin-right", "16px")],
136 "mt-1" => vec![("margin-top", "4px")],
137 "mt-2" => vec![("margin-top", "8px")],
138 "mt-4" => vec![("margin-top", "16px")],
139 "mt-8" => vec![("margin-top", "32px")],
140 "mx-4" => vec![("margin-left", "16px"), ("margin-right", "16px")],
141 "mx-auto" => vec![("margin-left", "auto"), ("margin-right", "auto")],
142 "my-4" => vec![("margin-bottom", "16px"), ("margin-top", "16px")],
143 "opacity-0" => vec![("opacity", "0")],
144 "opacity-100" => vec![("opacity", "1")],
145 "opacity-25" => vec![("opacity", "0.25")],
146 "opacity-50" => vec![("opacity", "0.5")],
147 "opacity-75" => vec![("opacity", "0.75")],
148 "overflow-auto" => vec![("overflow", "auto")],
149 "overflow-hidden" => vec![("overflow", "hidden")],
150 "overflow-scroll" => vec![("overflow", "scroll")],
151 "overflow-visible" => vec![("overflow", "visible")],
152 "p-0" => vec![("padding", "0px")],
153 "p-1" => vec![("padding", "4px")],
154 "p-10" => vec![("padding", "40px")],
155 "p-12" => vec![("padding", "48px")],
156 "p-2" => vec![("padding", "8px")],
157 "p-3" => vec![("padding", "12px")],
158 "p-4" => vec![("padding", "16px")],
159 "p-5" => vec![("padding", "20px")],
160 "p-6" => vec![("padding", "24px")],
161 "p-8" => vec![("padding", "32px")],
162 "pb-4" => vec![("padding-bottom", "16px")],
163 "pl-4" => vec![("padding-left", "16px")],
164 "pr-4" => vec![("padding-right", "16px")],
165 "pt-4" => vec![("padding-top", "16px")],
166 "px-1" => vec![("padding-left", "4px"), ("padding-right", "4px")],
167 "px-2" => vec![("padding-left", "8px"), ("padding-right", "8px")],
168 "px-3" => vec![("padding-left", "12px"), ("padding-right", "12px")],
169 "px-4" => vec![("padding-left", "16px"), ("padding-right", "16px")],
170 "px-6" => vec![("padding-left", "24px"), ("padding-right", "24px")],
171 "px-8" => vec![("padding-left", "32px"), ("padding-right", "32px")],
172 "py-1" => vec![("padding-bottom", "4px"), ("padding-top", "4px")],
173 "py-2" => vec![("padding-bottom", "8px"), ("padding-top", "8px")],
174 "py-3" => vec![("padding-bottom", "12px"), ("padding-top", "12px")],
175 "py-4" => vec![("padding-bottom", "16px"), ("padding-top", "16px")],
176 "py-6" => vec![("padding-bottom", "24px"), ("padding-top", "24px")],
177 "relative" => vec![("position", "relative")],
178 "rotate-0" => vec![("transform", "rotate(0deg)")],
179 "rotate-1" => vec![("transform", "rotate(1deg)")],
180 "rotate-12" => vec![("transform", "rotate(12deg)")],
181 "rotate-180" => vec![("transform", "rotate(180deg)")],
182 "rotate-2" => vec![("transform", "rotate(2deg)")],
183 "rotate-3" => vec![("transform", "rotate(3deg)")],
184 "rotate-45" => vec![("transform", "rotate(45deg)")],
185 "rotate-6" => vec![("transform", "rotate(6deg)")],
186 "rotate-90" => vec![("transform", "rotate(90deg)")],
187 "rounded" => vec![("border-radius", "4px")],
188 "rounded-2xl" => vec![("border-radius", "16px")],
189 "rounded-3xl" => vec![("border-radius", "24px")],
190 "rounded-full" => vec![("border-radius", "9999px")],
191 "rounded-lg" => vec![("border-radius", "8px")],
192 "rounded-md" => vec![("border-radius", "6px")],
193 "rounded-none" => vec![("border-radius", "0px")],
194 "rounded-sm" => vec![("border-radius", "2px")],
195 "rounded-xl" => vec![("border-radius", "12px")],
196 "scale-0" => vec![("transform", "scale(0)")],
197 "scale-100" => vec![("transform", "scale(1)")],
198 "scale-105" => vec![("transform", "scale(1.05)")],
199 "scale-110" => vec![("transform", "scale(1.1)")],
200 "scale-125" => vec![("transform", "scale(1.25)")],
201 "scale-150" => vec![("transform", "scale(1.5)")],
202 "scale-50" => vec![("transform", "scale(0.5)")],
203 "scale-75" => vec![("transform", "scale(0.75)")],
204 "scale-90" => vec![("transform", "scale(0.9)")],
205 "scale-95" => vec![("transform", "scale(0.95)")],
206 "scale-x-0" => vec![("transform", "scaleX(0)")],
207 "scale-x-100" => vec![("transform", "scaleX(1)")],
208 "scale-x-105" => vec![("transform", "scaleX(1.05)")],
209 "scale-x-110" => vec![("transform", "scaleX(1.1)")],
210 "scale-x-125" => vec![("transform", "scaleX(1.25)")],
211 "scale-x-150" => vec![("transform", "scaleX(1.5)")],
212 "scale-x-50" => vec![("transform", "scaleX(0.5)")],
213 "scale-x-75" => vec![("transform", "scaleX(0.75)")],
214 "scale-x-90" => vec![("transform", "scaleX(0.9)")],
215 "scale-x-95" => vec![("transform", "scaleX(0.95)")],
216 "scale-y-0" => vec![("transform", "scaleY(0)")],
217 "scale-y-100" => vec![("transform", "scaleY(1)")],
218 "scale-y-105" => vec![("transform", "scaleY(1.05)")],
219 "scale-y-110" => vec![("transform", "scaleY(1.1)")],
220 "scale-y-125" => vec![("transform", "scaleY(1.25)")],
221 "scale-y-150" => vec![("transform", "scaleY(1.5)")],
222 "scale-y-50" => vec![("transform", "scaleY(0.5)")],
223 "scale-y-75" => vec![("transform", "scaleY(0.75)")],
224 "scale-y-90" => vec![("transform", "scaleY(0.9)")],
225 "scale-y-95" => vec![("transform", "scaleY(0.95)")],
226 "select-all" => vec![("user-select", "all")],
227 "select-none" => vec![("user-select", "none")],
228 "select-text" => vec![("user-select", "text")],
229 "skew-x-0" => vec![("transform", "skewX(0deg)")],
230 "skew-x-1" => vec![("transform", "skewX(1deg)")],
231 "skew-x-12" => vec![("transform", "skewX(12deg)")],
232 "skew-x-2" => vec![("transform", "skewX(2deg)")],
233 "skew-x-3" => vec![("transform", "skewX(3deg)")],
234 "skew-x-6" => vec![("transform", "skewX(6deg)")],
235 "skew-y-0" => vec![("transform", "skewY(0deg)")],
236 "skew-y-1" => vec![("transform", "skewY(1deg)")],
237 "skew-y-12" => vec![("transform", "skewY(12deg)")],
238 "skew-y-2" => vec![("transform", "skewY(2deg)")],
239 "skew-y-3" => vec![("transform", "skewY(3deg)")],
240 "skew-y-6" => vec![("transform", "skewY(6deg)")],
241 "static" => vec![("position", "static")],
242 "sticky" => vec![("position", "sticky")],
243 "text-2xl" => vec![("font-size", "24px"), ("line-height", "32px")],
244 "text-3xl" => vec![("font-size", "30px"), ("line-height", "36px")],
245 "text-4xl" => vec![("font-size", "36px"), ("line-height", "40px")],
246 "text-base" => vec![("font-size", "16px"), ("line-height", "24px")],
247 "text-black" => vec![("color", "#000000")],
248 "text-blue-500" => vec![("color", "#3b82f6")],
249 "text-center" => vec![("text-align", "center")],
250 "text-gray-400" => vec![("color", "#9ca3af")],
251 "text-gray-500" => vec![("color", "#6b7280")],
252 "text-gray-600" => vec![("color", "#4b5563")],
253 "text-gray-700" => vec![("color", "#374151")],
254 "text-gray-800" => vec![("color", "#1f2937")],
255 "text-gray-900" => vec![("color", "#111827")],
256 "text-green-500" => vec![("color", "#22c55e")],
257 "text-left" => vec![("text-align", "left")],
258 "text-lg" => vec![("font-size", "18px"), ("line-height", "28px")],
259 "text-purple-500" => vec![("color", "#a855f7")],
260 "text-red-500" => vec![("color", "#ef4444")],
261 "text-right" => vec![("text-align", "right")],
262 "text-sm" => vec![("font-size", "14px"), ("line-height", "20px")],
263 "text-white" => vec![("color", "#ffffff")],
264 "text-xl" => vec![("font-size", "20px"), ("line-height", "28px")],
265 "text-xs" => vec![("font-size", "12px"), ("line-height", "16px")],
266 "translate-x-0" => vec![("transform", "translateX(0px)")],
267 "translate-x-1" => vec![("transform", "translateX(4px)")],
268 "translate-x-10" => vec![("transform", "translateX(40px)")],
269 "translate-x-12" => vec![("transform", "translateX(48px)")],
270 "translate-x-16" => vec![("transform", "translateX(64px)")],
271 "translate-x-2" => vec![("transform", "translateX(8px)")],
272 "translate-x-20" => vec![("transform", "translateX(80px)")],
273 "translate-x-24" => vec![("transform", "translateX(96px)")],
274 "translate-x-3" => vec![("transform", "translateX(12px)")],
275 "translate-x-32" => vec![("transform", "translateX(128px)")],
276 "translate-x-4" => vec![("transform", "translateX(16px)")],
277 "translate-x-40" => vec![("transform", "translateX(160px)")],
278 "translate-x-48" => vec![("transform", "translateX(192px)")],
279 "translate-x-5" => vec![("transform", "translateX(20px)")],
280 "translate-x-56" => vec![("transform", "translateX(224px)")],
281 "translate-x-6" => vec![("transform", "translateX(24px)")],
282 "translate-x-64" => vec![("transform", "translateX(256px)")],
283 "translate-x-8" => vec![("transform", "translateX(32px)")],
284 "translate-y-0" => vec![("transform", "translateY(0px)")],
285 "translate-y-1" => vec![("transform", "translateY(4px)")],
286 "translate-y-10" => vec![("transform", "translateY(40px)")],
287 "translate-y-12" => vec![("transform", "translateY(48px)")],
288 "translate-y-16" => vec![("transform", "translateY(64px)")],
289 "translate-y-2" => vec![("transform", "translateY(8px)")],
290 "translate-y-20" => vec![("transform", "translateY(80px)")],
291 "translate-y-24" => vec![("transform", "translateY(96px)")],
292 "translate-y-3" => vec![("transform", "translateY(12px)")],
293 "translate-y-32" => vec![("transform", "translateY(128px)")],
294 "translate-y-4" => vec![("transform", "translateY(16px)")],
295 "translate-y-40" => vec![("transform", "translateY(160px)")],
296 "translate-y-48" => vec![("transform", "translateY(192px)")],
297 "translate-y-5" => vec![("transform", "translateY(20px)")],
298 "translate-y-56" => vec![("transform", "translateY(224px)")],
299 "translate-y-6" => vec![("transform", "translateY(24px)")],
300 "translate-y-64" => vec![("transform", "translateY(256px)")],
301 "translate-y-8" => vec![("transform", "translateY(32px)")],
302 "w-0" => vec![("width", "0px")],
303 "w-1" => vec![("width", "4px")],
304 "w-10" => vec![("width", "40px")],
305 "w-12" => vec![("width", "48px")],
306 "w-16" => vec![("width", "64px")],
307 "w-2" => vec![("width", "8px")],
308 "w-20" => vec![("width", "80px")],
309 "w-24" => vec![("width", "96px")],
310 "w-32" => vec![("width", "128px")],
311 "w-4" => vec![("width", "16px")],
312 "w-48" => vec![("width", "192px")],
313 "w-6" => vec![("width", "24px")],
314 "w-64" => vec![("width", "256px")],
315 "w-8" => vec![("width", "32px")],
316 "w-auto" => vec![("width", "auto")],
317 "w-full" => vec![("width", "100%")],
318 "w-screen" => vec![("width", "100vw")],
319 "z-0" => vec![("z-index", "0")],
320 "z-10" => vec![("z-index", "10")],
321 "z-20" => vec![("z-index", "20")],
322 "z-30" => vec![("z-index", "30")],
323 "z-40" => vec![("z-index", "40")],
324 "z-50" => vec![("z-index", "50")],
325 "z-auto" => vec![("z-index", "auto")],
326 _ => return None,
327 })
328}
329
330fn resolve_negative(class: &str) -> Option<Vec<(String, String)>> {
334 let pair = |k: &str, v: String| Some(vec![(k.to_string(), v)]);
335 if let Some(rest) = class.strip_prefix("-z-") {
336 if !rest.is_empty() && rest.trim_start_matches('-').chars().all(|c| c.is_ascii_digit()) {
337 let val: i64 = rest.parse().unwrap_or(0);
338 return pair("z-index", (-val).to_string());
339 }
340 return None;
341 }
342 let body = class.strip_prefix('-')?;
343 let (name, num) = body.rsplit_once('-')?;
344 let n: i64 = num.parse().ok()?;
345 match name {
346 "translate-x" => pair("transform", format!("translateX(-{}px)", n * 4)),
347 "translate-y" => pair("transform", format!("translateY(-{}px)", n * 4)),
348 "rotate" => pair("transform", format!("rotate(-{}deg)", n)),
349 "skew-x" => pair("transform", format!("skewX(-{}deg)", n)),
350 "skew-y" => pair("transform", format!("skewY(-{}deg)", n)),
351 _ => None,
352 }
353}
354
355fn parse_arbitrary(class: &str) -> Option<Vec<(String, String)>> {
359 let single = |k: &str, v: &str| Some(vec![(k.to_string(), v.to_string())]);
360 let dash = class.find("-[")?;
361 if !class.ends_with(']') {
362 return None;
363 }
364 let prefix = &class[..dash];
365 let value = &class[dash + 2..class.len() - 1];
366 if prefix.is_empty() || value.is_empty() {
367 return None;
368 }
369 if !prefix
370 .trim_start_matches('-')
371 .chars()
372 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
373 {
374 return None;
375 }
376 let (negated, base) = match prefix.strip_prefix('-') {
377 Some(rest) => (true, rest),
378 None => (false, prefix),
379 };
380 if let Some(func) = transform_fn(base) {
381 let val = if negated { negate_value(value) } else { value.to_string() };
382 return single("transform", &format!("{}({})", func, val));
383 }
384 if negated {
385 return None;
386 }
387 match base {
391 "bg" => single("background-color", value),
392 "text" => single("color", value),
393 "w" => single("width", value),
394 "h" => single("height", value),
395 "p" => single("padding", value),
396 "px" => Some(vec![
397 ("padding-left".to_string(), value.to_string()),
398 ("padding-right".to_string(), value.to_string()),
399 ]),
400 "py" => Some(vec![
401 ("padding-top".to_string(), value.to_string()),
402 ("padding-bottom".to_string(), value.to_string()),
403 ]),
404 "m" => single("margin", value),
405 "mt" => single("margin-top", value),
406 "mb" => single("margin-bottom", value),
407 "ml" => single("margin-left", value),
408 "mr" => single("margin-right", value),
409 "gap" => single("gap", value),
410 "rounded" => single("border-radius", value),
411 "opacity" => single("opacity", value),
412 "top" => single("top", value),
413 "bottom" => single("bottom", value),
414 "left" => single("left", value),
415 "right" => single("right", value),
416 "min-w" => single("min-width", value),
417 "min-h" => single("min-height", value),
418 "max-w" => single("max-width", value),
419 "max-h" => single("max-height", value),
420 "z" => single("z-index", value),
421 "font" => single("font-weight", value),
422 _ => None,
423 }
424}
425
426fn transform_fn(name: &str) -> Option<&'static str> {
427 Some(match name {
428 "translate-x" => "translateX",
429 "translate-y" => "translateY",
430 "rotate" => "rotate",
431 "scale-x" => "scaleX",
432 "scale-y" => "scaleY",
433 "scale" => "scale",
434 "skew-x" => "skewX",
435 "skew-y" => "skewY",
436 _ => return None,
437 })
438}
439
440fn negate_value(value: &str) -> String {
442 let v = value.trim();
443 if let Some(rest) = v.strip_prefix('-') {
444 return rest.to_string();
445 }
446 if let Some(rest) = v.strip_prefix('+') {
447 return format!("-{}", rest);
448 }
449 format!("-{}", v)
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 fn resolved(class: &str) -> HashMap<String, String> {
457 TailwindResolver::new().resolve(class)
458 }
459
460 #[test]
461 fn static_entries_cover_all_tiers() {
462 let flex = resolved("flex-1");
464 assert_eq!(flex.get("flex-grow").map(String::as_str), Some("1"));
465 assert_eq!(flex.get("flex-shrink").map(String::as_str), Some("1"));
466 assert_eq!(flex.get("flex-basis").map(String::as_str), Some("0%"));
467 let sm = resolved("text-sm");
469 assert_eq!(sm.get("font-size").map(String::as_str), Some("14px"));
470 assert_eq!(sm.get("line-height").map(String::as_str), Some("20px"));
471 let px = resolved("px-4");
473 assert_eq!(px.get("padding-left").map(String::as_str), Some("16px"));
474 assert_eq!(px.get("padding-right").map(String::as_str), Some("16px"));
475 let merged = TailwindResolver::new().resolve_many("text-white text-black");
477 assert_eq!(merged.get("color").map(String::as_str), Some("#000000"));
478 }
479
480 #[test]
481 fn negative_utilities_negate_numerically() {
482 assert_eq!(resolved("-z-10").get("z-index").map(String::as_str), Some("-10"));
483 assert_eq!(resolved("-z-0").get("z-index").map(String::as_str), Some("0"));
484 assert_eq!(
485 resolved("-translate-x-4").get("transform").map(String::as_str),
486 Some("translateX(-16px)")
487 );
488 assert_eq!(
489 resolved("-rotate-45").get("transform").map(String::as_str),
490 Some("rotate(-45deg)")
491 );
492 assert_eq!(
493 resolved("-skew-y-12").get("transform").map(String::as_str),
494 Some("skewY(-12deg)")
495 );
496 assert!(resolved("-mt-4").is_empty());
497 }
498
499 #[test]
500 fn arbitrary_values_cover_every_prefix() {
501 assert_eq!(
502 resolved("bg-[#ff0000]").get("background-color").map(String::as_str),
503 Some("#ff0000")
504 );
505 assert_eq!(resolved("w-[200px]").get("width").map(String::as_str), Some("200px"));
506 assert_eq!(resolved("text-[red]").get("color").map(String::as_str), Some("red"));
507 assert_eq!(resolved("z-[5]").get("z-index").map(String::as_str), Some("5"));
508 assert_eq!(resolved("font-[700]").get("font-weight").map(String::as_str), Some("700"));
509 assert_eq!(resolved("min-w-[10px]").get("min-width").map(String::as_str), Some("10px"));
510 let px = resolved("px-[3px]");
512 assert_eq!(px.get("padding-left").map(String::as_str), Some("3px"));
513 assert_eq!(px.get("padding-right").map(String::as_str), Some("3px"));
514 let py = resolved("py-[3px]");
515 assert_eq!(py.get("padding-top").map(String::as_str), Some("3px"));
516 assert_eq!(py.get("padding-bottom").map(String::as_str), Some("3px"));
517 }
518
519 #[test]
520 fn arbitrary_transforms_support_negation() {
521 assert_eq!(
522 resolved("rotate-[45deg]").get("transform").map(String::as_str),
523 Some("rotate(45deg)")
524 );
525 assert_eq!(
526 resolved("-translate-x-[50%]").get("transform").map(String::as_str),
527 Some("translateX(-50%)")
528 );
529 assert_eq!(
530 resolved("translate-y-[-3px]").get("transform").map(String::as_str),
531 Some("translateY(-3px)")
532 );
533 }
534
535 #[test]
536 fn unknown_and_malformed_classes_resolve_empty() {
537 for cls in ["bogus-class", "bg-", "w-[]", "bg-[unclosed", "-z-", "-rotate-", "px-[]"] {
538 assert!(resolved(cls).is_empty(), "{} should resolve empty", cls);
539 }
540 }
541}