waterui_form/lib.rs
1//! # `WaterUI` Form Components
2//!
3//! This crate provides a comprehensive form system for `WaterUI` applications with ergonomic
4//! macros and type-safe form building capabilities.
5
6#![no_std]
7extern crate alloc;
8
9use waterui_color::Color;
10
11/// Picker form component module.
12pub mod picker;
13pub mod valid;
14
15use waterui_core::{AnyView, Binding, Str, View};
16
17/// Trait for types that can be automatically converted to form UI components.
18///
19/// This trait enables ergonomic form creation by mapping Rust data structures
20/// to interactive UI components. It can be implemented manually for custom layouts
21/// or automatically derived using `#[derive(FormBuilder)]`.
22///
23/// # Automatic Implementation via Derive Macro
24///
25/// The most convenient way to use this trait is through the derive macro:
26///
27/// ```text
28/// use waterui_form::FormBuilder;
29///
30/// #[derive(Default, Clone, Debug, FormBuilder)]
31/// pub struct UserProfile {
32/// /// User's display name
33/// pub name: String,
34/// /// Account active status
35/// pub active: bool,
36/// /// User's current level
37/// pub level: i32,
38/// }
39/// ```
40///
41/// This automatically generates appropriate form components for each field type.
42///
43/// # Manual Implementation
44///
45/// For custom layouts or specialized form behavior, implement the trait manually:
46///
47/// ```text
48/// use waterui_form::FormBuilder;
49/// use waterui::prelude::*;
50///
51/// struct CustomForm {
52/// title: String,
53/// enabled: bool,
54/// }
55///
56/// impl FormBuilder for CustomForm {
57/// type View = VStack;
58///
59/// fn view(binding: &Binding<Self>) -> Self::View {
60/// vstack((
61/// TextField::new(&binding.title),
62/// Toggle::new(&binding.enabled),
63/// ))
64/// }
65/// }
66/// ```
67pub trait FormBuilder: Sized {
68 /// The view type that represents this form field.
69 ///
70 /// This associated type determines what UI component will be rendered
71 /// for this form. For derived implementations, this is typically a
72 /// layout containing multiple form fields.
73 type View: View;
74
75 /// Creates a view representation of this form field bound to the given binding.
76 ///
77 /// This method is the core of the form building system. It takes a binding
78 /// to the form data and returns a view that displays interactive form controls.
79 ///
80 /// # Parameters
81 ///
82 /// * `binding` - A reference to a binding that holds the form's data state
83 ///
84 /// # Returns
85 ///
86 /// A view that renders the form's UI components, automatically bound to the data
87 fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View;
88
89 /// Creates a new binding with the default value for this form.
90 ///
91 /// This convenience method creates a new binding initialized with the default
92 /// values for all form fields. It requires the form type to implement
93 /// [`Default`] and [`Clone`].
94 ///
95 /// # Examples
96 ///
97 /// ```text
98 /// # use waterui_form::FormBuilder;
99 /// # #[derive(Default, Clone, FormBuilder)]
100 /// # struct LoginForm { username: String, password: String }
101 /// let form_binding = LoginForm::binding();
102 /// // form_binding now holds a LoginForm with default values
103 /// ```
104 #[must_use]
105 fn binding() -> Binding<Self>
106 where
107 Self: Default + Clone,
108 {
109 Binding::default()
110 }
111}
112
113// TextField has prompt, so handle it specially
114impl FormBuilder for Str {
115 type View = waterui_controls::TextField;
116 #[allow(unused_variables)]
117 fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
118 let mut field = waterui_controls::TextField::new(binding).label(label);
119 if !placeholder.is_empty() {
120 field = field.prompt(placeholder);
121 }
122 field
123 }
124}
125
126// String also uses TextField with prompt
127impl FormBuilder for alloc::string::String {
128 type View = waterui_controls::TextField;
129
130 fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
131 use alloc::string::ToString;
132 use nami::Binding as NamiBinding;
133 // Map String to Str binding
134 let str_binding = NamiBinding::mapping(binding, Str::from, |binding, str_val: Str| {
135 *binding.get_mut() = str_val.to_string();
136 });
137 let mut field = waterui_controls::TextField::new(&str_binding).label(label);
138 if !placeholder.is_empty() {
139 field = field.prompt(placeholder);
140 }
141 field
142 }
143}
144
145// Other components don't have prompt
146impl FormBuilder for i32 {
147 type View = waterui_controls::Stepper;
148 #[allow(unused_variables)]
149 fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
150 waterui_controls::Stepper::new(binding)
151 .label(label)
152 .range(Self::MIN..=Self::MAX)
153 }
154}
155
156impl FormBuilder for bool {
157 type View = waterui_controls::Toggle;
158 #[allow(unused_variables)]
159 fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
160 waterui_controls::Toggle::new(binding).label(label)
161 }
162}
163
164impl FormBuilder for Color {
165 type View = picker::ColorPicker;
166 #[allow(unused_variables)]
167 fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
168 picker::ColorPicker::new(binding).label(label)
169 }
170}
171
172impl FormBuilder for f64 {
173 type View = waterui_controls::Slider;
174 #[allow(unused_variables)]
175 fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
176 waterui_controls::Slider::new(0.0..=1.0, binding).label(label)
177 }
178}
179
180impl FormBuilder for f32 {
181 type View = waterui_controls::Slider;
182 #[allow(unused_variables)]
183 fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
184 waterui_controls::Slider::new(
185 0.0..=1.0,
186 #[allow(clippy::cast_possible_truncation)]
187 &Binding::mapping(binding, f64::from, |binding, val| binding.set(val as Self)),
188 )
189 .label(label)
190 }
191}
192
193/// Secure form components for handling sensitive data like passwords.
194pub mod secure;
195pub use secure::{SecureField, secure};
196
197/// Creates a form view from a binding to a type that implements [`FormBuilder`].
198///
199/// This is the primary entry point for creating forms using the `FormBuilder` system.
200/// It takes a binding to your form data structure and returns a view that renders
201/// all the appropriate form controls.
202///
203/// # Examples
204///
205/// ## Basic Usage
206///
207/// ```text
208/// use waterui_form::{FormBuilder, form};
209/// use waterui_core::{Binding, View};
210///
211/// #[derive(Default, Clone, Debug, FormBuilder)]
212/// struct ContactForm {
213/// name: String,
214/// email: String,
215/// age: i32,
216/// newsletter: bool,
217/// }
218///
219/// fn contact_form_view() -> impl View {
220/// let form_binding = ContactForm::binding();
221/// form(&form_binding)
222/// }
223/// ```
224///
225/// ## With Custom Initialization
226///
227/// ```text
228/// # use waterui_form::{FormBuilder, form};
229/// # use waterui_core::{Binding, View};
230/// # #[derive(Default, Clone, Debug, FormBuilder)]
231/// # struct ContactForm { name: String, email: String }
232/// fn pre_filled_form() -> impl View {
233/// let initial_data = ContactForm {
234/// name: "John Doe".to_string(),
235/// email: "john@example.com".to_string(),
236/// };
237/// let form_binding = Binding::new(initial_data);
238/// form(&form_binding)
239/// }
240/// ```
241///
242/// ## Accessing Form Data
243///
244/// ```text
245/// # use waterui_form::{FormBuilder, form};
246/// # use waterui_core::{Binding, View};
247/// # use waterui_layout::vstack;
248/// # use waterui_text::text;
249/// # #[derive(Default, Clone, Debug, FormBuilder)]
250/// # struct ContactForm { name: String, email: String }
251/// fn form_with_output() -> impl View {
252/// let form_binding = ContactForm::binding();
253///
254/// vstack((
255/// form(&form_binding),
256/// // Display current form values
257/// text!(format!("Name: {}", form_binding.name.get())),
258/// text!(format!("Email: {}", form_binding.email.get())),
259/// ))
260/// }
261/// ```
262///
263/// # Parameters
264///
265/// * `binding` - A reference to a binding containing the form's data
266///
267/// # Returns
268///
269/// A view that renders interactive form controls for all fields in the bound data structure
270#[must_use]
271pub fn form<T: FormBuilder>(binding: &Binding<T>) -> T::View {
272 T::view(binding, AnyView::default(), Str::default())
273}