Skip to main content

cranpose_foundation/text/
line_limits.rs

1/// Line limit configuration for text fields.
2///
3/// Controls whether a text field allows multiple lines of input and how many
4/// lines are visible at minimum and maximum.
5///
6/// # SingleLine
7///
8/// When `SingleLine` is used:
9/// - Newline characters (`\n`) are blocked from input
10/// - Pasted text has newlines replaced with spaces
11/// - The text field scrolls horizontally if content exceeds width
12/// - The Enter key does NOT insert a newline (may trigger submit action)
13///
14/// # MultiLine
15///
16/// When `MultiLine` is used:
17/// - Newline characters are allowed
18/// - The text field scrolls vertically if content exceeds visible lines
19/// - `min_lines` controls minimum visible height (default: 1)
20/// - `max_lines` controls maximum visible height before scrolling (default: unlimited)
21///
22/// # Example
23///
24/// ```
25/// use cranpose_foundation::text::TextFieldLineLimits;
26///
27/// // Single-line text field (like a search box)
28/// let single = TextFieldLineLimits::SingleLine;
29///
30/// // Multi-line with default settings
31/// let multi = TextFieldLineLimits::default();
32///
33/// // Multi-line with 3-5 visible lines
34/// let constrained = TextFieldLineLimits::MultiLine {
35///     min_lines: 3,
36///     max_lines: 5,
37/// };
38/// ```
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum TextFieldLineLimits {
41    /// Single line input - no newlines allowed, horizontal scrolling.
42    SingleLine,
43    /// Multi-line input with optional line constraints.
44    ///
45    /// - `min_lines`: Minimum number of visible lines (affects minimum height)
46    /// - `max_lines`: Maximum number of visible lines before scrolling
47    MultiLine {
48        /// Minimum visible lines (default: 1)
49        min_lines: usize,
50        /// Maximum visible lines before scrolling (default: unlimited)
51        max_lines: usize,
52    },
53}
54
55impl TextFieldLineLimits {
56    /// Default multi-line with no constraints (1 line minimum, unlimited maximum).
57    pub const DEFAULT: Self = Self::MultiLine {
58        min_lines: 1,
59        max_lines: usize::MAX,
60    };
61
62    /// Returns true if this is single-line mode.
63    #[inline]
64    pub fn is_single_line(&self) -> bool {
65        matches!(self, Self::SingleLine)
66    }
67
68    /// Returns true if this is multi-line mode.
69    #[inline]
70    pub fn is_multi_line(&self) -> bool {
71        matches!(self, Self::MultiLine { .. })
72    }
73
74    /// Returns the minimum number of lines (1 for SingleLine).
75    pub fn min_lines(&self) -> usize {
76        match self {
77            Self::SingleLine => 1,
78            Self::MultiLine { min_lines, .. } => *min_lines,
79        }
80    }
81
82    /// Returns the maximum number of lines (1 for SingleLine, configured value for MultiLine).
83    pub fn max_lines(&self) -> usize {
84        match self {
85            Self::SingleLine => 1,
86            Self::MultiLine { max_lines, .. } => *max_lines,
87        }
88    }
89}
90
91impl Default for TextFieldLineLimits {
92    fn default() -> Self {
93        Self::DEFAULT
94    }
95}
96
97/// Filters text for single-line mode by replacing newlines with spaces.
98///
99/// This is used when:
100/// - Pasting text into a SingleLine text field
101/// - Programmatically setting text on a SingleLine field
102///
103/// # Example
104///
105/// ```
106/// use cranpose_foundation::text::filter_for_single_line;
107///
108/// assert_eq!(filter_for_single_line("hello\nworld"), "hello world");
109/// assert_eq!(filter_for_single_line("a\n\nb"), "a  b");
110/// ```
111pub fn filter_for_single_line(text: &str) -> String {
112    text.replace('\n', " ")
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn single_line_properties() {
121        let limits = TextFieldLineLimits::SingleLine;
122        assert!(limits.is_single_line());
123        assert!(!limits.is_multi_line());
124        assert_eq!(limits.min_lines(), 1);
125        assert_eq!(limits.max_lines(), 1);
126    }
127
128    #[test]
129    fn multi_line_default_properties() {
130        let limits = TextFieldLineLimits::default();
131        assert!(!limits.is_single_line());
132        assert!(limits.is_multi_line());
133        assert_eq!(limits.min_lines(), 1);
134        assert_eq!(limits.max_lines(), usize::MAX);
135    }
136
137    #[test]
138    fn multi_line_constrained_properties() {
139        let limits = TextFieldLineLimits::MultiLine {
140            min_lines: 3,
141            max_lines: 10,
142        };
143        assert!(!limits.is_single_line());
144        assert!(limits.is_multi_line());
145        assert_eq!(limits.min_lines(), 3);
146        assert_eq!(limits.max_lines(), 10);
147    }
148
149    #[test]
150    fn filter_replaces_newlines() {
151        assert_eq!(filter_for_single_line("hello\nworld"), "hello world");
152        assert_eq!(filter_for_single_line("a\n\nb"), "a  b");
153        assert_eq!(filter_for_single_line("no newlines"), "no newlines");
154        assert_eq!(filter_for_single_line("\n\n\n"), "   ");
155    }
156}