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