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}