tailwind_rs_core/utilities/
interactivity.rs

1//! Interactivity utilities for tailwind-rs
2//!
3//! This module provides utilities for CSS interactivity including cursor,
4//! pointer events, resize, scroll behavior, scroll snap, touch action,
5//! user select, and will change.
6
7use crate::classes::ClassBuilder;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11/// Cursor values
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum Cursor {
14    /// Auto cursor
15    Auto,
16    /// Default cursor
17    Default,
18    /// Pointer cursor
19    Pointer,
20    /// Wait cursor
21    Wait,
22    /// Text cursor
23    Text,
24    /// Move cursor
25    Move,
26    /// Help cursor
27    Help,
28    /// Not allowed cursor
29    NotAllowed,
30    /// None cursor
31    None,
32    /// Context menu cursor
33    ContextMenu,
34    /// Progress cursor
35    Progress,
36    /// Cell cursor
37    Cell,
38    /// Crosshair cursor
39    Crosshair,
40    /// Vertical text cursor
41    VerticalText,
42    /// Alias cursor
43    Alias,
44    /// Copy cursor
45    Copy,
46    /// No drop cursor
47    NoDrop,
48    /// Grab cursor
49    Grab,
50    /// Grabbing cursor
51    Grabbing,
52    /// All scroll cursor
53    AllScroll,
54    /// Col resize cursor
55    ColResize,
56    /// Row resize cursor
57    RowResize,
58    /// N resize cursor
59    NResize,
60    /// E resize cursor
61    EResize,
62    /// S resize cursor
63    SResize,
64    /// W resize cursor
65    WResize,
66    /// Ne resize cursor
67    NeResize,
68    /// Nw resize cursor
69    NwResize,
70    /// Se resize cursor
71    SeResize,
72    /// Sw resize cursor
73    SwResize,
74    /// Ew resize cursor
75    EwResize,
76    /// Ns resize cursor
77    NsResize,
78    /// Nesw resize cursor
79    NeswResize,
80    /// Nwse resize cursor
81    NwseResize,
82    /// Zoom in cursor
83    ZoomIn,
84    /// Zoom out cursor
85    ZoomOut,
86}
87
88/// Pointer events values
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
90pub enum PointerEvents {
91    /// Auto pointer events
92    Auto,
93    /// None pointer events
94    None,
95}
96
97/// Resize values
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
99pub enum Resize {
100    /// None resize
101    None,
102    /// Both resize
103    Both,
104    /// Horizontal resize
105    Horizontal,
106    /// Vertical resize
107    Vertical,
108    /// Block resize
109    Block,
110    /// Inline resize
111    Inline,
112}
113
114/// Scroll behavior values
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
116pub enum ScrollBehavior {
117    /// Auto scroll behavior
118    Auto,
119    /// Smooth scroll behavior
120    Smooth,
121}
122
123/// Scroll snap type values
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
125pub enum ScrollSnapType {
126    /// None scroll snap
127    None,
128    /// X scroll snap
129    X,
130    /// Y scroll snap
131    Y,
132    /// Both scroll snap
133    Both,
134    /// Mandatory scroll snap
135    Mandatory,
136    /// Proximity scroll snap
137    Proximity,
138}
139
140/// Scroll snap align values
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
142pub enum ScrollSnapAlign {
143    /// Start scroll snap align
144    Start,
145    /// End scroll snap align
146    End,
147    /// Center scroll snap align
148    Center,
149    /// None scroll snap align
150    None,
151}
152
153/// Touch action values
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
155pub enum TouchAction {
156    /// Auto touch action
157    Auto,
158    /// None touch action
159    None,
160    /// Pan X touch action
161    PanX,
162    /// Pan Y touch action
163    PanY,
164    /// Pan left touch action
165    PanLeft,
166    /// Pan right touch action
167    PanRight,
168    /// Pan up touch action
169    PanUp,
170    /// Pan down touch action
171    PanDown,
172    /// Manipulation touch action
173    Manipulation,
174}
175
176/// User select values
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
178pub enum UserSelect {
179    /// None user select
180    None,
181    /// Text user select
182    Text,
183    /// All user select
184    All,
185    /// Auto user select
186    Auto,
187}
188
189/// Will change values
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
191pub enum WillChange {
192    /// Auto will change
193    Auto,
194    /// Scroll position will change
195    ScrollPosition,
196    /// Contents will change
197    Contents,
198    /// Transform will change
199    Transform,
200}
201
202impl Cursor {
203    pub fn to_class_name(&self) -> String {
204        match self {
205            Cursor::Auto => "auto".to_string(),
206            Cursor::Default => "default".to_string(),
207            Cursor::Pointer => "pointer".to_string(),
208            Cursor::Wait => "wait".to_string(),
209            Cursor::Text => "text".to_string(),
210            Cursor::Move => "move".to_string(),
211            Cursor::Help => "help".to_string(),
212            Cursor::NotAllowed => "not-allowed".to_string(),
213            Cursor::None => "none".to_string(),
214            Cursor::ContextMenu => "context-menu".to_string(),
215            Cursor::Progress => "progress".to_string(),
216            Cursor::Cell => "cell".to_string(),
217            Cursor::Crosshair => "crosshair".to_string(),
218            Cursor::VerticalText => "vertical-text".to_string(),
219            Cursor::Alias => "alias".to_string(),
220            Cursor::Copy => "copy".to_string(),
221            Cursor::NoDrop => "no-drop".to_string(),
222            Cursor::Grab => "grab".to_string(),
223            Cursor::Grabbing => "grabbing".to_string(),
224            Cursor::AllScroll => "all-scroll".to_string(),
225            Cursor::ColResize => "col-resize".to_string(),
226            Cursor::RowResize => "row-resize".to_string(),
227            Cursor::NResize => "n-resize".to_string(),
228            Cursor::EResize => "e-resize".to_string(),
229            Cursor::SResize => "s-resize".to_string(),
230            Cursor::WResize => "w-resize".to_string(),
231            Cursor::NeResize => "ne-resize".to_string(),
232            Cursor::NwResize => "nw-resize".to_string(),
233            Cursor::SeResize => "se-resize".to_string(),
234            Cursor::SwResize => "sw-resize".to_string(),
235            Cursor::EwResize => "ew-resize".to_string(),
236            Cursor::NsResize => "ns-resize".to_string(),
237            Cursor::NeswResize => "nesw-resize".to_string(),
238            Cursor::NwseResize => "nwse-resize".to_string(),
239            Cursor::ZoomIn => "zoom-in".to_string(),
240            Cursor::ZoomOut => "zoom-out".to_string(),
241        }
242    }
243    
244    pub fn to_css_value(&self) -> String {
245        match self {
246            Cursor::Auto => "auto".to_string(),
247            Cursor::Default => "default".to_string(),
248            Cursor::Pointer => "pointer".to_string(),
249            Cursor::Wait => "wait".to_string(),
250            Cursor::Text => "text".to_string(),
251            Cursor::Move => "move".to_string(),
252            Cursor::Help => "help".to_string(),
253            Cursor::NotAllowed => "not-allowed".to_string(),
254            Cursor::None => "none".to_string(),
255            Cursor::ContextMenu => "context-menu".to_string(),
256            Cursor::Progress => "progress".to_string(),
257            Cursor::Cell => "cell".to_string(),
258            Cursor::Crosshair => "crosshair".to_string(),
259            Cursor::VerticalText => "vertical-text".to_string(),
260            Cursor::Alias => "alias".to_string(),
261            Cursor::Copy => "copy".to_string(),
262            Cursor::NoDrop => "no-drop".to_string(),
263            Cursor::Grab => "grab".to_string(),
264            Cursor::Grabbing => "grabbing".to_string(),
265            Cursor::AllScroll => "all-scroll".to_string(),
266            Cursor::ColResize => "col-resize".to_string(),
267            Cursor::RowResize => "row-resize".to_string(),
268            Cursor::NResize => "n-resize".to_string(),
269            Cursor::EResize => "e-resize".to_string(),
270            Cursor::SResize => "s-resize".to_string(),
271            Cursor::WResize => "w-resize".to_string(),
272            Cursor::NeResize => "ne-resize".to_string(),
273            Cursor::NwResize => "nw-resize".to_string(),
274            Cursor::SeResize => "se-resize".to_string(),
275            Cursor::SwResize => "sw-resize".to_string(),
276            Cursor::EwResize => "ew-resize".to_string(),
277            Cursor::NsResize => "ns-resize".to_string(),
278            Cursor::NeswResize => "nesw-resize".to_string(),
279            Cursor::NwseResize => "nwse-resize".to_string(),
280            Cursor::ZoomIn => "zoom-in".to_string(),
281            Cursor::ZoomOut => "zoom-out".to_string(),
282        }
283    }
284}
285
286impl PointerEvents {
287    pub fn to_class_name(&self) -> String {
288        match self {
289            PointerEvents::Auto => "auto".to_string(),
290            PointerEvents::None => "none".to_string(),
291        }
292    }
293    
294    pub fn to_css_value(&self) -> String {
295        match self {
296            PointerEvents::Auto => "auto".to_string(),
297            PointerEvents::None => "none".to_string(),
298        }
299    }
300}
301
302impl Resize {
303    pub fn to_class_name(&self) -> String {
304        match self {
305            Resize::None => "none".to_string(),
306            Resize::Both => "both".to_string(),
307            Resize::Horizontal => "horizontal".to_string(),
308            Resize::Vertical => "vertical".to_string(),
309            Resize::Block => "block".to_string(),
310            Resize::Inline => "inline".to_string(),
311        }
312    }
313    
314    pub fn to_css_value(&self) -> String {
315        match self {
316            Resize::None => "none".to_string(),
317            Resize::Both => "both".to_string(),
318            Resize::Horizontal => "horizontal".to_string(),
319            Resize::Vertical => "vertical".to_string(),
320            Resize::Block => "block".to_string(),
321            Resize::Inline => "inline".to_string(),
322        }
323    }
324}
325
326impl ScrollBehavior {
327    pub fn to_class_name(&self) -> String {
328        match self {
329            ScrollBehavior::Auto => "auto".to_string(),
330            ScrollBehavior::Smooth => "smooth".to_string(),
331        }
332    }
333    
334    pub fn to_css_value(&self) -> String {
335        match self {
336            ScrollBehavior::Auto => "auto".to_string(),
337            ScrollBehavior::Smooth => "smooth".to_string(),
338        }
339    }
340}
341
342impl ScrollSnapType {
343    pub fn to_class_name(&self) -> String {
344        match self {
345            ScrollSnapType::None => "none".to_string(),
346            ScrollSnapType::X => "x".to_string(),
347            ScrollSnapType::Y => "y".to_string(),
348            ScrollSnapType::Both => "both".to_string(),
349            ScrollSnapType::Mandatory => "mandatory".to_string(),
350            ScrollSnapType::Proximity => "proximity".to_string(),
351        }
352    }
353    
354    pub fn to_css_value(&self) -> String {
355        match self {
356            ScrollSnapType::None => "none".to_string(),
357            ScrollSnapType::X => "x".to_string(),
358            ScrollSnapType::Y => "y".to_string(),
359            ScrollSnapType::Both => "both".to_string(),
360            ScrollSnapType::Mandatory => "mandatory".to_string(),
361            ScrollSnapType::Proximity => "proximity".to_string(),
362        }
363    }
364}
365
366impl ScrollSnapAlign {
367    pub fn to_class_name(&self) -> String {
368        match self {
369            ScrollSnapAlign::Start => "start".to_string(),
370            ScrollSnapAlign::End => "end".to_string(),
371            ScrollSnapAlign::Center => "center".to_string(),
372            ScrollSnapAlign::None => "none".to_string(),
373        }
374    }
375    
376    pub fn to_css_value(&self) -> String {
377        match self {
378            ScrollSnapAlign::Start => "start".to_string(),
379            ScrollSnapAlign::End => "end".to_string(),
380            ScrollSnapAlign::Center => "center".to_string(),
381            ScrollSnapAlign::None => "none".to_string(),
382        }
383    }
384}
385
386impl TouchAction {
387    pub fn to_class_name(&self) -> String {
388        match self {
389            TouchAction::Auto => "auto".to_string(),
390            TouchAction::None => "none".to_string(),
391            TouchAction::PanX => "pan-x".to_string(),
392            TouchAction::PanY => "pan-y".to_string(),
393            TouchAction::PanLeft => "pan-left".to_string(),
394            TouchAction::PanRight => "pan-right".to_string(),
395            TouchAction::PanUp => "pan-up".to_string(),
396            TouchAction::PanDown => "pan-down".to_string(),
397            TouchAction::Manipulation => "manipulation".to_string(),
398        }
399    }
400    
401    pub fn to_css_value(&self) -> String {
402        match self {
403            TouchAction::Auto => "auto".to_string(),
404            TouchAction::None => "none".to_string(),
405            TouchAction::PanX => "pan-x".to_string(),
406            TouchAction::PanY => "pan-y".to_string(),
407            TouchAction::PanLeft => "pan-left".to_string(),
408            TouchAction::PanRight => "pan-right".to_string(),
409            TouchAction::PanUp => "pan-up".to_string(),
410            TouchAction::PanDown => "pan-down".to_string(),
411            TouchAction::Manipulation => "manipulation".to_string(),
412        }
413    }
414}
415
416impl UserSelect {
417    pub fn to_class_name(&self) -> String {
418        match self {
419            UserSelect::None => "none".to_string(),
420            UserSelect::Text => "text".to_string(),
421            UserSelect::All => "all".to_string(),
422            UserSelect::Auto => "auto".to_string(),
423        }
424    }
425    
426    pub fn to_css_value(&self) -> String {
427        match self {
428            UserSelect::None => "none".to_string(),
429            UserSelect::Text => "text".to_string(),
430            UserSelect::All => "all".to_string(),
431            UserSelect::Auto => "auto".to_string(),
432        }
433    }
434}
435
436impl WillChange {
437    pub fn to_class_name(&self) -> String {
438        match self {
439            WillChange::Auto => "auto".to_string(),
440            WillChange::ScrollPosition => "scroll-position".to_string(),
441            WillChange::Contents => "contents".to_string(),
442            WillChange::Transform => "transform".to_string(),
443        }
444    }
445    
446    pub fn to_css_value(&self) -> String {
447        match self {
448            WillChange::Auto => "auto".to_string(),
449            WillChange::ScrollPosition => "scroll-position".to_string(),
450            WillChange::Contents => "contents".to_string(),
451            WillChange::Transform => "transform".to_string(),
452        }
453    }
454}
455
456impl fmt::Display for Cursor {
457    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458        write!(f, "{}", self.to_class_name())
459    }
460}
461
462impl fmt::Display for PointerEvents {
463    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
464        write!(f, "{}", self.to_class_name())
465    }
466}
467
468impl fmt::Display for Resize {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        write!(f, "{}", self.to_class_name())
471    }
472}
473
474impl fmt::Display for ScrollBehavior {
475    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476        write!(f, "{}", self.to_class_name())
477    }
478}
479
480impl fmt::Display for ScrollSnapType {
481    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482        write!(f, "{}", self.to_class_name())
483    }
484}
485
486impl fmt::Display for ScrollSnapAlign {
487    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488        write!(f, "{}", self.to_class_name())
489    }
490}
491
492impl fmt::Display for TouchAction {
493    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
494        write!(f, "{}", self.to_class_name())
495    }
496}
497
498impl fmt::Display for UserSelect {
499    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500        write!(f, "{}", self.to_class_name())
501    }
502}
503
504impl fmt::Display for WillChange {
505    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506        write!(f, "{}", self.to_class_name())
507    }
508}
509
510/// Trait for adding cursor utilities to a class builder
511pub trait CursorUtilities {
512    fn cursor(self, cursor: Cursor) -> Self;
513}
514
515impl CursorUtilities for ClassBuilder {
516    fn cursor(self, cursor: Cursor) -> Self {
517        self.class(format!("cursor-{}", cursor.to_class_name()))
518    }
519}
520
521/// Trait for adding pointer events utilities to a class builder
522pub trait PointerEventsUtilities {
523    fn pointer_events(self, events: PointerEvents) -> Self;
524}
525
526impl PointerEventsUtilities for ClassBuilder {
527    fn pointer_events(self, events: PointerEvents) -> Self {
528        self.class(format!("pointer-events-{}", events.to_class_name()))
529    }
530}
531
532/// Trait for adding resize utilities to a class builder
533pub trait ResizeUtilities {
534    fn resize(self, resize: Resize) -> Self;
535}
536
537impl ResizeUtilities for ClassBuilder {
538    fn resize(self, resize: Resize) -> Self {
539        self.class(format!("resize-{}", resize.to_class_name()))
540    }
541}
542
543/// Trait for adding scroll behavior utilities to a class builder
544pub trait ScrollBehaviorUtilities {
545    fn scroll_behavior(self, behavior: ScrollBehavior) -> Self;
546}
547
548impl ScrollBehaviorUtilities for ClassBuilder {
549    fn scroll_behavior(self, behavior: ScrollBehavior) -> Self {
550        self.class(format!("scroll-{}", behavior.to_class_name()))
551    }
552}
553
554/// Trait for adding scroll snap type utilities to a class builder
555pub trait ScrollSnapTypeUtilities {
556    fn scroll_snap_type(self, snap_type: ScrollSnapType) -> Self;
557}
558
559impl ScrollSnapTypeUtilities for ClassBuilder {
560    fn scroll_snap_type(self, snap_type: ScrollSnapType) -> Self {
561        self.class(format!("snap-{}", snap_type.to_class_name()))
562    }
563}
564
565/// Trait for adding scroll snap align utilities to a class builder
566pub trait ScrollSnapAlignUtilities {
567    fn scroll_snap_align(self, align: ScrollSnapAlign) -> Self;
568}
569
570impl ScrollSnapAlignUtilities for ClassBuilder {
571    fn scroll_snap_align(self, align: ScrollSnapAlign) -> Self {
572        self.class(format!("snap-align-{}", align.to_class_name()))
573    }
574}
575
576/// Trait for adding touch action utilities to a class builder
577pub trait TouchActionUtilities {
578    fn touch_action(self, action: TouchAction) -> Self;
579}
580
581impl TouchActionUtilities for ClassBuilder {
582    fn touch_action(self, action: TouchAction) -> Self {
583        self.class(format!("touch-{}", action.to_class_name()))
584    }
585}
586
587/// Trait for adding user select utilities to a class builder
588pub trait UserSelectUtilities {
589    fn user_select(self, select: UserSelect) -> Self;
590}
591
592impl UserSelectUtilities for ClassBuilder {
593    fn user_select(self, select: UserSelect) -> Self {
594        self.class(format!("select-{}", select.to_class_name()))
595    }
596}
597
598/// Trait for adding will change utilities to a class builder
599pub trait WillChangeUtilities {
600    fn will_change(self, change: WillChange) -> Self;
601}
602
603impl WillChangeUtilities for ClassBuilder {
604    fn will_change(self, change: WillChange) -> Self {
605        self.class(format!("will-change-{}", change.to_class_name()))
606    }
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612    
613    #[test]
614    fn test_cursor_utilities() {
615        let classes = ClassBuilder::new()
616            .cursor(Cursor::Auto)
617            .cursor(Cursor::Default)
618            .cursor(Cursor::Pointer)
619            .cursor(Cursor::Wait)
620            .cursor(Cursor::Text)
621            .cursor(Cursor::Move)
622            .cursor(Cursor::Help)
623            .cursor(Cursor::NotAllowed)
624            .cursor(Cursor::None)
625            .cursor(Cursor::Grab)
626            .cursor(Cursor::Grabbing)
627            .build();
628        
629        let css_classes = classes.to_css_classes();
630        assert!(css_classes.contains("cursor-auto"));
631        assert!(css_classes.contains("cursor-default"));
632        assert!(css_classes.contains("cursor-pointer"));
633        assert!(css_classes.contains("cursor-wait"));
634        assert!(css_classes.contains("cursor-text"));
635        assert!(css_classes.contains("cursor-move"));
636        assert!(css_classes.contains("cursor-help"));
637        assert!(css_classes.contains("cursor-not-allowed"));
638        assert!(css_classes.contains("cursor-none"));
639        assert!(css_classes.contains("cursor-grab"));
640        assert!(css_classes.contains("cursor-grabbing"));
641    }
642    
643    #[test]
644    fn test_pointer_events_utilities() {
645        let classes = ClassBuilder::new()
646            .pointer_events(PointerEvents::Auto)
647            .pointer_events(PointerEvents::None)
648            .build();
649        
650        let css_classes = classes.to_css_classes();
651        assert!(css_classes.contains("pointer-events-auto"));
652        assert!(css_classes.contains("pointer-events-none"));
653    }
654    
655    #[test]
656    fn test_resize_utilities() {
657        let classes = ClassBuilder::new()
658            .resize(Resize::None)
659            .resize(Resize::Both)
660            .resize(Resize::Horizontal)
661            .resize(Resize::Vertical)
662            .resize(Resize::Block)
663            .resize(Resize::Inline)
664            .build();
665        
666        let css_classes = classes.to_css_classes();
667        assert!(css_classes.contains("resize-none"));
668        assert!(css_classes.contains("resize-both"));
669        assert!(css_classes.contains("resize-horizontal"));
670        assert!(css_classes.contains("resize-vertical"));
671        assert!(css_classes.contains("resize-block"));
672        assert!(css_classes.contains("resize-inline"));
673    }
674    
675    #[test]
676    fn test_scroll_behavior_utilities() {
677        let classes = ClassBuilder::new()
678            .scroll_behavior(ScrollBehavior::Auto)
679            .scroll_behavior(ScrollBehavior::Smooth)
680            .build();
681        
682        let css_classes = classes.to_css_classes();
683        assert!(css_classes.contains("scroll-auto"));
684        assert!(css_classes.contains("scroll-smooth"));
685    }
686    
687    #[test]
688    fn test_scroll_snap_type_utilities() {
689        let classes = ClassBuilder::new()
690            .scroll_snap_type(ScrollSnapType::None)
691            .scroll_snap_type(ScrollSnapType::X)
692            .scroll_snap_type(ScrollSnapType::Y)
693            .scroll_snap_type(ScrollSnapType::Both)
694            .scroll_snap_type(ScrollSnapType::Mandatory)
695            .scroll_snap_type(ScrollSnapType::Proximity)
696            .build();
697        
698        let css_classes = classes.to_css_classes();
699        assert!(css_classes.contains("snap-none"));
700        assert!(css_classes.contains("snap-x"));
701        assert!(css_classes.contains("snap-y"));
702        assert!(css_classes.contains("snap-both"));
703        assert!(css_classes.contains("snap-mandatory"));
704        assert!(css_classes.contains("snap-proximity"));
705    }
706    
707    #[test]
708    fn test_scroll_snap_align_utilities() {
709        let classes = ClassBuilder::new()
710            .scroll_snap_align(ScrollSnapAlign::Start)
711            .scroll_snap_align(ScrollSnapAlign::End)
712            .scroll_snap_align(ScrollSnapAlign::Center)
713            .scroll_snap_align(ScrollSnapAlign::None)
714            .build();
715        
716        let css_classes = classes.to_css_classes();
717        assert!(css_classes.contains("snap-align-start"));
718        assert!(css_classes.contains("snap-align-end"));
719        assert!(css_classes.contains("snap-align-center"));
720        assert!(css_classes.contains("snap-align-none"));
721    }
722    
723    #[test]
724    fn test_touch_action_utilities() {
725        let classes = ClassBuilder::new()
726            .touch_action(TouchAction::Auto)
727            .touch_action(TouchAction::None)
728            .touch_action(TouchAction::PanX)
729            .touch_action(TouchAction::PanY)
730            .touch_action(TouchAction::PanLeft)
731            .touch_action(TouchAction::PanRight)
732            .touch_action(TouchAction::PanUp)
733            .touch_action(TouchAction::PanDown)
734            .touch_action(TouchAction::Manipulation)
735            .build();
736        
737        let css_classes = classes.to_css_classes();
738        assert!(css_classes.contains("touch-auto"));
739        assert!(css_classes.contains("touch-none"));
740        assert!(css_classes.contains("touch-pan-x"));
741        assert!(css_classes.contains("touch-pan-y"));
742        assert!(css_classes.contains("touch-pan-left"));
743        assert!(css_classes.contains("touch-pan-right"));
744        assert!(css_classes.contains("touch-pan-up"));
745        assert!(css_classes.contains("touch-pan-down"));
746        assert!(css_classes.contains("touch-manipulation"));
747    }
748    
749    #[test]
750    fn test_user_select_utilities() {
751        let classes = ClassBuilder::new()
752            .user_select(UserSelect::None)
753            .user_select(UserSelect::Text)
754            .user_select(UserSelect::All)
755            .user_select(UserSelect::Auto)
756            .build();
757        
758        let css_classes = classes.to_css_classes();
759        assert!(css_classes.contains("select-none"));
760        assert!(css_classes.contains("select-text"));
761        assert!(css_classes.contains("select-all"));
762        assert!(css_classes.contains("select-auto"));
763    }
764    
765    #[test]
766    fn test_will_change_utilities() {
767        let classes = ClassBuilder::new()
768            .will_change(WillChange::Auto)
769            .will_change(WillChange::ScrollPosition)
770            .will_change(WillChange::Contents)
771            .will_change(WillChange::Transform)
772            .build();
773        
774        let css_classes = classes.to_css_classes();
775        assert!(css_classes.contains("will-change-auto"));
776        assert!(css_classes.contains("will-change-scroll-position"));
777        assert!(css_classes.contains("will-change-contents"));
778        assert!(css_classes.contains("will-change-transform"));
779    }
780    
781    #[test]
782    fn test_complex_interactivity_combination() {
783        let classes = ClassBuilder::new()
784            .cursor(Cursor::Pointer)
785            .pointer_events(PointerEvents::Auto)
786            .resize(Resize::Both)
787            .scroll_behavior(ScrollBehavior::Smooth)
788            .scroll_snap_type(ScrollSnapType::X)
789            .scroll_snap_align(ScrollSnapAlign::Center)
790            .touch_action(TouchAction::PanX)
791            .user_select(UserSelect::None)
792            .will_change(WillChange::Transform)
793            .build();
794        
795        let css_classes = classes.to_css_classes();
796        assert!(css_classes.contains("cursor-pointer"));
797        assert!(css_classes.contains("pointer-events-auto"));
798        assert!(css_classes.contains("resize-both"));
799        assert!(css_classes.contains("scroll-smooth"));
800        assert!(css_classes.contains("snap-x"));
801        assert!(css_classes.contains("snap-align-center"));
802        assert!(css_classes.contains("touch-pan-x"));
803        assert!(css_classes.contains("select-none"));
804        assert!(css_classes.contains("will-change-transform"));
805    }
806    
807    /// Test that all Week 13 interactivity utilities are implemented
808    #[test]
809    fn test_week13_interactivity_utilities() {
810        // Test all Week 13 interactivity utilities
811        let classes = ClassBuilder::new()
812            // Cursor utilities
813            .cursor(Cursor::Auto)
814            .cursor(Cursor::Default)
815            .cursor(Cursor::Pointer)
816            .cursor(Cursor::Wait)
817            .cursor(Cursor::Text)
818            .cursor(Cursor::Move)
819            .cursor(Cursor::Help)
820            .cursor(Cursor::NotAllowed)
821            .cursor(Cursor::None)
822            .cursor(Cursor::ContextMenu)
823            .cursor(Cursor::Progress)
824            .cursor(Cursor::Cell)
825            .cursor(Cursor::Crosshair)
826            .cursor(Cursor::VerticalText)
827            .cursor(Cursor::Alias)
828            .cursor(Cursor::Copy)
829            .cursor(Cursor::NoDrop)
830            .cursor(Cursor::Grab)
831            .cursor(Cursor::Grabbing)
832            // Pointer events utilities
833            .pointer_events(PointerEvents::Auto)
834            .pointer_events(PointerEvents::None)
835            // Resize utilities
836            .resize(Resize::None)
837            .resize(Resize::Both)
838            .resize(Resize::Horizontal)
839            .resize(Resize::Vertical)
840            // Scroll utilities
841            .scroll_behavior(ScrollBehavior::Auto)
842            .scroll_behavior(ScrollBehavior::Smooth)
843            .scroll_snap_type(ScrollSnapType::None)
844            .scroll_snap_type(ScrollSnapType::X)
845            .scroll_snap_type(ScrollSnapType::Y)
846            .scroll_snap_type(ScrollSnapType::Both)
847            .scroll_snap_align(ScrollSnapAlign::None)
848            .scroll_snap_align(ScrollSnapAlign::Start)
849            .scroll_snap_align(ScrollSnapAlign::End)
850            .scroll_snap_align(ScrollSnapAlign::Center)
851            // Touch action utilities
852            .touch_action(TouchAction::Auto)
853            .touch_action(TouchAction::None)
854            .touch_action(TouchAction::PanX)
855            .touch_action(TouchAction::PanY)
856            .touch_action(TouchAction::PanLeft)
857            .touch_action(TouchAction::PanRight)
858            .touch_action(TouchAction::PanUp)
859            .touch_action(TouchAction::PanDown)
860            .touch_action(TouchAction::Manipulation)
861            // User select utilities
862            .user_select(UserSelect::None)
863            .user_select(UserSelect::Text)
864            .user_select(UserSelect::All)
865            .user_select(UserSelect::Auto)
866            .build();
867        
868        let css_classes = classes.to_css_classes();
869        
870        // Cursor utilities
871        assert!(css_classes.contains("cursor-auto"));
872        assert!(css_classes.contains("cursor-default"));
873        assert!(css_classes.contains("cursor-pointer"));
874        assert!(css_classes.contains("cursor-wait"));
875        assert!(css_classes.contains("cursor-text"));
876        assert!(css_classes.contains("cursor-move"));
877        assert!(css_classes.contains("cursor-help"));
878        assert!(css_classes.contains("cursor-not-allowed"));
879        assert!(css_classes.contains("cursor-none"));
880        assert!(css_classes.contains("cursor-context-menu"));
881        assert!(css_classes.contains("cursor-progress"));
882        assert!(css_classes.contains("cursor-cell"));
883        assert!(css_classes.contains("cursor-crosshair"));
884        assert!(css_classes.contains("cursor-vertical-text"));
885        assert!(css_classes.contains("cursor-alias"));
886        assert!(css_classes.contains("cursor-copy"));
887        assert!(css_classes.contains("cursor-no-drop"));
888        assert!(css_classes.contains("cursor-grab"));
889        assert!(css_classes.contains("cursor-grabbing"));
890        
891        // Pointer events utilities
892        assert!(css_classes.contains("pointer-events-auto"));
893        assert!(css_classes.contains("pointer-events-none"));
894        
895        // Resize utilities
896        assert!(css_classes.contains("resize-none"));
897        assert!(css_classes.contains("resize-both"));
898        assert!(css_classes.contains("resize-horizontal"));
899        assert!(css_classes.contains("resize-vertical"));
900        
901        // Scroll utilities
902        assert!(css_classes.contains("scroll-auto"));
903        assert!(css_classes.contains("scroll-smooth"));
904        assert!(css_classes.contains("snap-none"));
905        assert!(css_classes.contains("snap-x"));
906        assert!(css_classes.contains("snap-y"));
907        assert!(css_classes.contains("snap-both"));
908        assert!(css_classes.contains("snap-align-none"));
909        assert!(css_classes.contains("snap-align-start"));
910        assert!(css_classes.contains("snap-align-end"));
911        assert!(css_classes.contains("snap-align-center"));
912        
913        // Touch action utilities
914        assert!(css_classes.contains("touch-auto"));
915        assert!(css_classes.contains("touch-none"));
916        assert!(css_classes.contains("touch-pan-x"));
917        assert!(css_classes.contains("touch-pan-y"));
918        assert!(css_classes.contains("touch-pan-left"));
919        assert!(css_classes.contains("touch-pan-right"));
920        assert!(css_classes.contains("touch-pan-up"));
921        assert!(css_classes.contains("touch-pan-down"));
922        assert!(css_classes.contains("touch-manipulation"));
923        
924        // User select utilities
925        assert!(css_classes.contains("select-none"));
926        assert!(css_classes.contains("select-text"));
927        assert!(css_classes.contains("select-all"));
928        assert!(css_classes.contains("select-auto"));
929    }
930}