1#![cfg_attr(target_arch = "wasm32", no_std)]
8
9#[cfg(target_arch = "wasm32")]
10extern crate alloc;
11
12use serde::{Deserialize, Serialize};
13use wasm_bindgen::prelude::*;
14
15#[cfg(target_arch = "wasm32")]
16use alloc::{string::String, vec::Vec, format, vec, string::ToString};
17
18#[cfg(not(target_arch = "wasm32"))]
19use std::{string::String, vec::Vec, format, vec, string::ToString};
20
21#[cfg(target_arch = "wasm32")]
22use core::fmt;
23
24#[cfg(not(target_arch = "wasm32"))]
25use std::fmt;
26
27#[cfg(target_arch = "wasm32")]
29pub fn init() {
30 console_error_panic_hook::set_once();
31 console_log::init_with_level(log::Level::Info).expect("Failed to initialize logging");
32}
33
34#[wasm_bindgen]
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct WasmClassBuilder {
38 classes: Vec<String>,
39}
40
41#[wasm_bindgen]
42impl WasmClassBuilder {
43 #[wasm_bindgen(constructor)]
45 pub fn new() -> WasmClassBuilder {
46 WasmClassBuilder {
47 classes: Vec::new(),
48 }
49 }
50
51 pub fn class(&mut self, class: &str) {
53 self.classes.push(class.to_string());
54 }
55
56 pub fn add_classes(&mut self, classes: &str) {
58 for class in classes.split_whitespace() {
59 if !class.is_empty() {
60 self.classes.push(class.to_string());
61 }
62 }
63 }
64
65 pub fn build(&self) -> String {
67 self.classes.join(" ")
68 }
69
70 pub fn len(&self) -> usize {
72 self.classes.len()
73 }
74
75 pub fn is_empty(&self) -> bool {
77 self.classes.is_empty()
78 }
79}
80
81impl Default for WasmClassBuilder {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89pub enum WasmBreakpoint {
90 Sm,
91 Md,
92 Lg,
93 Xl,
94 Xl2,
95}
96
97impl WasmBreakpoint {
98 pub fn media_query(&self) -> &'static str {
100 match self {
101 WasmBreakpoint::Sm => "(min-width: 640px)",
102 WasmBreakpoint::Md => "(min-width: 768px)",
103 WasmBreakpoint::Lg => "(min-width: 1024px)",
104 WasmBreakpoint::Xl => "(min-width: 1280px)",
105 WasmBreakpoint::Xl2 => "(min-width: 1536px)",
106 }
107 }
108
109 pub fn prefix(&self) -> &'static str {
111 match self {
112 WasmBreakpoint::Sm => "sm:",
113 WasmBreakpoint::Md => "md:",
114 WasmBreakpoint::Lg => "lg:",
115 WasmBreakpoint::Xl => "xl:",
116 WasmBreakpoint::Xl2 => "2xl:",
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
123pub enum WasmSpacing {
124 Px,
125 Zero,
126 P1,
127 P2,
128 P3,
129 P4,
130 P5,
131 P6,
132 P8,
133 P10,
134 P12,
135 P16,
136 P20,
137 P24,
138 P32,
139 P40,
140 P48,
141 P56,
142 P64,
143}
144
145impl WasmSpacing {
146 pub fn css_value(&self) -> &'static str {
148 match self {
149 WasmSpacing::Px => "1px",
150 WasmSpacing::Zero => "0px",
151 WasmSpacing::P1 => "0.25rem",
152 WasmSpacing::P2 => "0.5rem",
153 WasmSpacing::P3 => "0.75rem",
154 WasmSpacing::P4 => "1rem",
155 WasmSpacing::P5 => "1.25rem",
156 WasmSpacing::P6 => "1.5rem",
157 WasmSpacing::P8 => "2rem",
158 WasmSpacing::P10 => "2.5rem",
159 WasmSpacing::P12 => "3rem",
160 WasmSpacing::P16 => "4rem",
161 WasmSpacing::P20 => "5rem",
162 WasmSpacing::P24 => "6rem",
163 WasmSpacing::P32 => "8rem",
164 WasmSpacing::P40 => "10rem",
165 WasmSpacing::P48 => "12rem",
166 WasmSpacing::P56 => "14rem",
167 WasmSpacing::P64 => "16rem",
168 }
169 }
170
171 pub fn padding_class(&self) -> String {
173 match self {
174 WasmSpacing::Px => "p-px".to_string(),
175 WasmSpacing::Zero => "p-0".to_string(),
176 WasmSpacing::P1 => "p-1".to_string(),
177 WasmSpacing::P2 => "p-2".to_string(),
178 WasmSpacing::P3 => "p-3".to_string(),
179 WasmSpacing::P4 => "p-4".to_string(),
180 WasmSpacing::P5 => "p-5".to_string(),
181 WasmSpacing::P6 => "p-6".to_string(),
182 WasmSpacing::P8 => "p-8".to_string(),
183 WasmSpacing::P10 => "p-10".to_string(),
184 WasmSpacing::P12 => "p-12".to_string(),
185 WasmSpacing::P16 => "p-16".to_string(),
186 WasmSpacing::P20 => "p-20".to_string(),
187 WasmSpacing::P24 => "p-24".to_string(),
188 WasmSpacing::P32 => "p-32".to_string(),
189 WasmSpacing::P40 => "p-40".to_string(),
190 WasmSpacing::P48 => "p-48".to_string(),
191 WasmSpacing::P56 => "p-56".to_string(),
192 WasmSpacing::P64 => "p-64".to_string(),
193 }
194 }
195
196 pub fn margin_class(&self) -> String {
198 match self {
199 WasmSpacing::Px => "m-px".to_string(),
200 WasmSpacing::Zero => "m-0".to_string(),
201 WasmSpacing::P1 => "m-1".to_string(),
202 WasmSpacing::P2 => "m-2".to_string(),
203 WasmSpacing::P3 => "m-3".to_string(),
204 WasmSpacing::P4 => "m-4".to_string(),
205 WasmSpacing::P5 => "m-5".to_string(),
206 WasmSpacing::P6 => "m-6".to_string(),
207 WasmSpacing::P8 => "m-8".to_string(),
208 WasmSpacing::P10 => "m-10".to_string(),
209 WasmSpacing::P12 => "m-12".to_string(),
210 WasmSpacing::P16 => "m-16".to_string(),
211 WasmSpacing::P20 => "m-20".to_string(),
212 WasmSpacing::P24 => "m-24".to_string(),
213 WasmSpacing::P32 => "m-32".to_string(),
214 WasmSpacing::P40 => "m-40".to_string(),
215 WasmSpacing::P48 => "m-48".to_string(),
216 WasmSpacing::P56 => "m-56".to_string(),
217 WasmSpacing::P64 => "m-64".to_string(),
218 }
219 }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
224pub enum WasmColor {
225 Gray50,
227 Gray100,
228 Gray200,
229 Gray300,
230 Gray400,
231 Gray500,
232 Gray600,
233 Gray700,
234 Gray800,
235 Gray900,
236 Blue50,
238 Blue100,
239 Blue200,
240 Blue300,
241 Blue400,
242 Blue500,
243 Blue600,
244 Blue700,
245 Blue800,
246 Blue900,
247 Red50,
249 Red100,
250 Red200,
251 Red300,
252 Red400,
253 Red500,
254 Red600,
255 Red700,
256 Red800,
257 Red900,
258 Green50,
260 Green100,
261 Green200,
262 Green300,
263 Green400,
264 Green500,
265 Green600,
266 Green700,
267 Green800,
268 Green900,
269 White,
271 Black,
272 Transparent,
273}
274
275impl WasmColor {
276 pub fn text_class(&self) -> String {
278 match self {
279 WasmColor::Gray50 => "text-gray-50".to_string(),
280 WasmColor::Gray100 => "text-gray-100".to_string(),
281 WasmColor::Gray200 => "text-gray-200".to_string(),
282 WasmColor::Gray300 => "text-gray-300".to_string(),
283 WasmColor::Gray400 => "text-gray-400".to_string(),
284 WasmColor::Gray500 => "text-gray-500".to_string(),
285 WasmColor::Gray600 => "text-gray-600".to_string(),
286 WasmColor::Gray700 => "text-gray-700".to_string(),
287 WasmColor::Gray800 => "text-gray-800".to_string(),
288 WasmColor::Gray900 => "text-gray-900".to_string(),
289 WasmColor::Blue50 => "text-blue-50".to_string(),
290 WasmColor::Blue100 => "text-blue-100".to_string(),
291 WasmColor::Blue200 => "text-blue-200".to_string(),
292 WasmColor::Blue300 => "text-blue-300".to_string(),
293 WasmColor::Blue400 => "text-blue-400".to_string(),
294 WasmColor::Blue500 => "text-blue-500".to_string(),
295 WasmColor::Blue600 => "text-blue-600".to_string(),
296 WasmColor::Blue700 => "text-blue-700".to_string(),
297 WasmColor::Blue800 => "text-blue-800".to_string(),
298 WasmColor::Blue900 => "text-blue-900".to_string(),
299 WasmColor::Red50 => "text-red-50".to_string(),
300 WasmColor::Red100 => "text-red-100".to_string(),
301 WasmColor::Red200 => "text-red-200".to_string(),
302 WasmColor::Red300 => "text-red-300".to_string(),
303 WasmColor::Red400 => "text-red-400".to_string(),
304 WasmColor::Red500 => "text-red-500".to_string(),
305 WasmColor::Red600 => "text-red-600".to_string(),
306 WasmColor::Red700 => "text-red-700".to_string(),
307 WasmColor::Red800 => "text-red-800".to_string(),
308 WasmColor::Red900 => "text-red-900".to_string(),
309 WasmColor::Green50 => "text-green-50".to_string(),
310 WasmColor::Green100 => "text-green-100".to_string(),
311 WasmColor::Green200 => "text-green-200".to_string(),
312 WasmColor::Green300 => "text-green-300".to_string(),
313 WasmColor::Green400 => "text-green-400".to_string(),
314 WasmColor::Green500 => "text-green-500".to_string(),
315 WasmColor::Green600 => "text-green-600".to_string(),
316 WasmColor::Green700 => "text-green-700".to_string(),
317 WasmColor::Green800 => "text-green-800".to_string(),
318 WasmColor::Green900 => "text-green-900".to_string(),
319 WasmColor::White => "text-white".to_string(),
320 WasmColor::Black => "text-black".to_string(),
321 WasmColor::Transparent => "text-transparent".to_string(),
322 }
323 }
324
325 pub fn bg_class(&self) -> String {
327 match self {
328 WasmColor::Gray50 => "bg-gray-50".to_string(),
329 WasmColor::Gray100 => "bg-gray-100".to_string(),
330 WasmColor::Gray200 => "bg-gray-200".to_string(),
331 WasmColor::Gray300 => "bg-gray-300".to_string(),
332 WasmColor::Gray400 => "bg-gray-400".to_string(),
333 WasmColor::Gray500 => "bg-gray-500".to_string(),
334 WasmColor::Gray600 => "bg-gray-600".to_string(),
335 WasmColor::Gray700 => "bg-gray-700".to_string(),
336 WasmColor::Gray800 => "bg-gray-800".to_string(),
337 WasmColor::Gray900 => "bg-gray-900".to_string(),
338 WasmColor::Blue50 => "bg-blue-50".to_string(),
339 WasmColor::Blue100 => "bg-blue-100".to_string(),
340 WasmColor::Blue200 => "bg-blue-200".to_string(),
341 WasmColor::Blue300 => "bg-blue-300".to_string(),
342 WasmColor::Blue400 => "bg-blue-400".to_string(),
343 WasmColor::Blue500 => "bg-blue-500".to_string(),
344 WasmColor::Blue600 => "bg-blue-600".to_string(),
345 WasmColor::Blue700 => "bg-blue-700".to_string(),
346 WasmColor::Blue800 => "bg-blue-800".to_string(),
347 WasmColor::Blue900 => "bg-blue-900".to_string(),
348 WasmColor::Red50 => "bg-red-50".to_string(),
349 WasmColor::Red100 => "bg-red-100".to_string(),
350 WasmColor::Red200 => "bg-red-200".to_string(),
351 WasmColor::Red300 => "bg-red-300".to_string(),
352 WasmColor::Red400 => "bg-red-400".to_string(),
353 WasmColor::Red500 => "bg-red-500".to_string(),
354 WasmColor::Red600 => "bg-red-600".to_string(),
355 WasmColor::Red700 => "bg-red-700".to_string(),
356 WasmColor::Red800 => "bg-red-800".to_string(),
357 WasmColor::Red900 => "bg-red-900".to_string(),
358 WasmColor::Green50 => "bg-green-50".to_string(),
359 WasmColor::Green100 => "bg-green-100".to_string(),
360 WasmColor::Green200 => "bg-green-200".to_string(),
361 WasmColor::Green300 => "bg-green-300".to_string(),
362 WasmColor::Green400 => "bg-green-400".to_string(),
363 WasmColor::Green500 => "bg-green-500".to_string(),
364 WasmColor::Green600 => "bg-green-600".to_string(),
365 WasmColor::Green700 => "bg-green-700".to_string(),
366 WasmColor::Green800 => "bg-green-800".to_string(),
367 WasmColor::Green900 => "bg-green-900".to_string(),
368 WasmColor::White => "bg-white".to_string(),
369 WasmColor::Black => "bg-black".to_string(),
370 WasmColor::Transparent => "bg-transparent".to_string(),
371 }
372 }
373}
374
375pub mod utils {
377 use super::*;
378
379 pub fn validate_class(class: &str) -> bool {
381 if class.is_empty() {
383 return false;
384 }
385
386 for ch in class.chars() {
388 if !ch.is_alphanumeric() && ch != '-' && ch != ':' && ch != '/' && ch != '[' && ch != ']' {
389 return false;
390 }
391 }
392
393 true
394 }
395
396 pub fn parse_responsive_class(class: &str) -> Option<(Option<WasmBreakpoint>, String)> {
398 for breakpoint in [WasmBreakpoint::Sm, WasmBreakpoint::Md, WasmBreakpoint::Lg, WasmBreakpoint::Xl, WasmBreakpoint::Xl2] {
399 if class.starts_with(breakpoint.prefix()) {
400 let base_class = &class[breakpoint.prefix().len()..];
401 return Some((Some(breakpoint), base_class.to_string()));
402 }
403 }
404
405 Some((None, class.to_string()))
406 }
407
408 pub fn generate_responsive_classes(base_class: &str, breakpoints: &[WasmBreakpoint]) -> Vec<String> {
410 let mut classes = vec![base_class.to_string()];
411
412 for breakpoint in breakpoints {
413 classes.push(format!("{}{}", breakpoint.prefix(), base_class));
414 }
415
416 classes
417 }
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
422pub enum WasmError {
423 InvalidClass(String),
424 ValidationError(String),
425 SerializationError(String),
426}
427
428impl fmt::Display for WasmError {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 match self {
431 WasmError::InvalidClass(msg) => write!(f, "Invalid class: {}", msg),
432 WasmError::ValidationError(msg) => write!(f, "Validation error: {}", msg),
433 WasmError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
434 }
435 }
436}
437
438#[cfg(not(target_arch = "wasm32"))]
439impl std::error::Error for WasmError {}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct WasmTheme {
444 pub name: String,
445 pub colors: Vec<WasmColor>,
446 pub spacing: Vec<WasmSpacing>,
447 pub breakpoints: Vec<WasmBreakpoint>,
448}
449
450impl WasmTheme {
451 pub fn new(name: String) -> Self {
453 Self {
454 name,
455 colors: vec![
456 WasmColor::White,
457 WasmColor::Black,
458 WasmColor::Gray500,
459 WasmColor::Blue500,
460 WasmColor::Red500,
461 WasmColor::Green500,
462 ],
463 spacing: vec![
464 WasmSpacing::P1,
465 WasmSpacing::P2,
466 WasmSpacing::P4,
467 WasmSpacing::P8,
468 WasmSpacing::P16,
469 ],
470 breakpoints: vec![
471 WasmBreakpoint::Sm,
472 WasmBreakpoint::Md,
473 WasmBreakpoint::Lg,
474 WasmBreakpoint::Xl,
475 ],
476 }
477 }
478
479 pub fn default() -> Self {
481 Self::new("default".to_string())
482 }
483}
484
485impl Default for WasmTheme {
486 fn default() -> Self {
487 Self::default()
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn test_wasm_class_builder() {
497 let mut builder = WasmClassBuilder::new();
498 builder.class("bg-blue-500");
499 builder.class("text-white");
500 builder.class("p-4");
501
502 assert_eq!(builder.clone().build(), "bg-blue-500 text-white p-4");
503 assert_eq!(builder.len(), 3);
504 assert!(!builder.is_empty());
505 }
506
507 #[test]
508 fn test_wasm_spacing() {
509 assert_eq!(WasmSpacing::P4.css_value(), "1rem");
510 assert_eq!(WasmSpacing::P4.padding_class(), "p-4");
511 assert_eq!(WasmSpacing::P4.margin_class(), "m-4");
512 }
513
514 #[test]
515 fn test_wasm_color() {
516 assert_eq!(WasmColor::Blue500.text_class(), "text-blue-500");
517 assert_eq!(WasmColor::Blue500.bg_class(), "bg-blue-500");
518 }
519
520 #[test]
521 fn test_wasm_breakpoint() {
522 assert_eq!(WasmBreakpoint::Md.media_query(), "(min-width: 768px)");
523 assert_eq!(WasmBreakpoint::Md.prefix(), "md:");
524 }
525
526 #[test]
527 fn test_utils_validate_class() {
528 assert!(utils::validate_class("bg-blue-500"));
529 assert!(utils::validate_class("text-white"));
530 assert!(!utils::validate_class(""));
531 assert!(!utils::validate_class("invalid@class"));
532 }
533
534 #[test]
535 fn test_utils_parse_responsive_class() {
536 let (bp, class) = utils::parse_responsive_class("md:bg-blue-500").unwrap();
537 assert_eq!(bp, Some(WasmBreakpoint::Md));
538 assert_eq!(class, "bg-blue-500");
539
540 let (bp, class) = utils::parse_responsive_class("bg-blue-500").unwrap();
541 assert_eq!(bp, None);
542 assert_eq!(class, "bg-blue-500");
543 }
544
545 #[test]
546 fn test_wasm_theme() {
547 let theme = WasmTheme::new("test".to_string());
548 assert_eq!(theme.name, "test");
549 assert!(!theme.colors.is_empty());
550 assert!(!theme.spacing.is_empty());
551 assert!(!theme.breakpoints.is_empty());
552 }
553}