cabin/html/elements/
input.rs

1mod auto_complete;
2
3use std::borrow::Cow;
4use std::fmt;
5
6pub use auto_complete::AutoComplete;
7use cabin_macros::Attribute;
8
9use super::button::{
10    Disabled, Form, FormAction, FormEnctype, FormMethod, FormNoValidate, FormTarget, Name,
11    PopoverTarget, PopoverTargetAction,
12};
13use super::common::Common;
14use super::global::Global;
15use super::img::Alt;
16use super::script::Src;
17use super::SerializeEventFn;
18use crate::error::InternalError;
19use crate::html::attributes::{Attributes, WithAttribute};
20use crate::html::events::InputEvent;
21use crate::html::{Aria, Html};
22
23pub fn input() -> Html<marker::Input, (), ()> {
24    Html::new("input", (), ())
25}
26
27pub mod marker {
28    pub struct Input;
29}
30
31impl<A: Attributes, V: 'static> Input for Html<marker::Input, A, V> {}
32impl<A: Attributes, V: 'static> Common for Html<marker::Input, A, V> {}
33impl<A: Attributes, V: 'static> Global for Html<marker::Input, A, V> {}
34impl<A: Attributes, V: 'static> Aria for Html<marker::Input, A, V> {}
35
36// TODO: typed inputs? (number, date, ...)
37/// TODO: doc comment
38pub trait Input: WithAttribute {
39    /// Hint for expected file type in file upload controls.
40    fn accept(self, accept: impl Into<Cow<'static, str>>) -> Self::Output<Accept> {
41        self.with_attribute(Accept(accept.into()))
42    }
43
44    /// Replacement text for use when images (type=image) are not available.
45    fn alt(self, alt: impl Into<Cow<'static, str>>) -> Self::Output<Alt> {
46        self.with_attribute(Alt(alt.into()))
47    }
48
49    /// Hint for form autofill feature.
50    fn autocomplete(self, autocomplete: AutoComplete) -> Self::Output<AutoComplete> {
51        self.with_attribute(autocomplete)
52    }
53
54    /// Whether the control is checked.
55    fn checked(self) -> Self::Output<Checked> {
56        self.with_checked(true)
57    }
58
59    /// Whether the control is checked.
60    fn with_checked(self, checked: bool) -> Self::Output<Checked> {
61        self.with_attribute(Checked(checked))
62    }
63
64    /// Name of form control to use for sending the element's directionality in form submission.
65    fn dirname(self, dirname: impl Into<Cow<'static, str>>) -> Self::Output<Dirname> {
66        self.with_attribute(Dirname(dirname.into()))
67    }
68
69    /// Whether the form control is disabled.
70    fn disabled(self) -> Self::Output<Disabled> {
71        self.with_disabled(true)
72    }
73
74    /// Whether the form control is disabled.
75    fn with_disabled(self, disabled: bool) -> Self::Output<Disabled> {
76        self.with_attribute(Disabled(disabled))
77    }
78
79    /// Associates the element with a [super::form] element.
80    fn form(self, form: impl Into<Cow<'static, str>>) -> Self::Output<Form> {
81        self.with_attribute(Form(form.into()))
82    }
83
84    /// URL to use for form submission.
85    fn form_action(self, form_action: impl Into<Cow<'static, str>>) -> Self::Output<FormAction> {
86        self.with_attribute(FormAction(form_action.into()))
87    }
88
89    /// Entry list encoding type to use for form submission.
90    fn form_enctype(self, form_enctype: impl Into<Cow<'static, str>>) -> Self::Output<FormEnctype> {
91        self.with_attribute(FormEnctype(form_enctype.into()))
92    }
93
94    /// Variant to use for form submission.
95    fn form_method(self, form_method: FormMethod) -> Self::Output<FormMethod> {
96        self.with_attribute(form_method)
97    }
98
99    /// Bypass form control validation for form submission.
100    fn form_novalidate(self) -> Self::Output<FormNoValidate> {
101        self.with_form_novalidate(true)
102    }
103
104    /// Bypass form control validation for form submission.
105    fn with_form_novalidate(self, form_novalidate: bool) -> Self::Output<FormNoValidate> {
106        self.with_attribute(FormNoValidate(form_novalidate))
107    }
108
109    /// Navigable for form submission.
110    fn form_target(self, form_target: impl Into<Cow<'static, str>>) -> Self::Output<FormTarget> {
111        self.with_attribute(FormTarget(form_target.into()))
112    }
113
114    /// Vertical dimension.
115    fn height(self, height: u32) -> Self::Output<Height> {
116        self.with_attribute(Height(height))
117    }
118
119    /// List of autocomplete options.
120    fn list(self, list: impl Into<Cow<'static, str>>) -> Self::Output<List> {
121        self.with_attribute(List(list.into()))
122    }
123
124    /// Maximum value
125    fn max(self, max: impl Into<Cow<'static, str>>) -> Self::Output<Max> {
126        self.with_attribute(Max(max.into()))
127    }
128
129    /// Maximum length of value.
130    fn max_length(self, max_length: i32) -> Self::Output<MaxLength> {
131        self.with_attribute(MaxLength(max_length))
132    }
133
134    /// Minimum value
135    fn min(self, min: impl Into<Cow<'static, str>>) -> Self::Output<Min> {
136        self.with_attribute(Min(min.into()))
137    }
138
139    /// Minimum length of value
140    fn min_length(self, min_length: i32) -> Self::Output<MinLength> {
141        self.with_attribute(MinLength(min_length))
142    }
143
144    /// Whether to allow multiple values.
145    fn multiple(self) -> Self::Output<Multiple> {
146        self.with_multiple(true)
147    }
148
149    /// Whether to allow multiple values.
150    fn with_multiple(self, multiple: bool) -> Self::Output<Multiple> {
151        self.with_attribute(Multiple(multiple))
152    }
153
154    /// Name of the element to use for form submission.
155    fn name(self, name: impl Into<Cow<'static, str>>) -> Self::Output<Name> {
156        self.with_attribute(Name(name.into()))
157    }
158
159    /// Pattern to be matched by the form control's value.
160    fn pattern(self, pattern: impl Into<Cow<'static, str>>) -> Self::Output<Pattern> {
161        self.with_attribute(Pattern(pattern.into()))
162    }
163
164    /// User-visible label to be placed within the form control.
165    fn placeholder(self, placeholder: impl Into<Cow<'static, str>>) -> Self::Output<Placeholder> {
166        self.with_attribute(Placeholder(placeholder.into()))
167    }
168
169    /// Targets a popover element to toggle, show, or hide.
170    fn popover_target(
171        self,
172        popover_target: impl Into<Cow<'static, str>>,
173    ) -> Self::Output<PopoverTarget> {
174        self.with_attribute(PopoverTarget(popover_target.into()))
175    }
176
177    /// Indicates whether a targeted popover element is to be toggled, shown, or hidden
178    fn popover_target_action(
179        self,
180        popover_target_action: PopoverTargetAction,
181    ) -> Self::Output<PopoverTargetAction> {
182        self.with_attribute(popover_target_action)
183    }
184
185    /// Whether to allow the value to be edited by the user
186    fn read_only(self) -> Self::Output<ReadOnly> {
187        self.with_read_only(true)
188    }
189
190    /// Whether to allow the value to be edited by the user
191    fn with_read_only(self, read_only: bool) -> Self::Output<ReadOnly> {
192        self.with_attribute(ReadOnly(read_only))
193    }
194
195    /// Whether the control is required for form submission
196    fn required(self) -> Self::Output<Required> {
197        self.with_required(true)
198    }
199
200    /// Whether the control is required for form submission
201    fn with_required(self, required: bool) -> Self::Output<Required> {
202        self.with_attribute(Required(required))
203    }
204
205    /// Size of the control
206    fn size(self, size: u32) -> Self::Output<Size> {
207        self.with_attribute(Size(size))
208    }
209
210    /// Address of the resource
211    fn src(self, src: impl Into<Cow<'static, str>>) -> Self::Output<Src> {
212        self.with_attribute(Src(src.into()))
213    }
214
215    /// Granularity to be matched by the form control's value
216    fn step(self, step: impl Into<Cow<'static, str>>) -> Self::Output<Step> {
217        self.with_attribute(Step(step.into()))
218    }
219
220    /// Type of form control.
221    fn r#type(self, r#type: Type) -> Self::Output<Type> {
222        self.with_attribute(r#type)
223    }
224
225    fn type_hidden(self) -> Self::Output<Type> {
226        self.r#type(Type::Hidden)
227    }
228
229    fn type_text(self) -> Self::Output<Type> {
230        self.r#type(Type::Text)
231    }
232
233    fn type_search(self) -> Self::Output<Type> {
234        self.r#type(Type::Search)
235    }
236
237    fn type_tel(self) -> Self::Output<Type> {
238        self.r#type(Type::Tel)
239    }
240
241    fn type_url(self) -> Self::Output<Type> {
242        self.r#type(Type::Url)
243    }
244
245    fn type_email(self) -> Self::Output<Type> {
246        self.r#type(Type::Email)
247    }
248
249    fn type_password(self) -> Self::Output<Type> {
250        self.r#type(Type::Password)
251    }
252
253    fn type_date(self) -> Self::Output<Type> {
254        self.r#type(Type::Date)
255    }
256
257    fn type_month(self) -> Self::Output<Type> {
258        self.r#type(Type::Month)
259    }
260
261    fn type_week(self) -> Self::Output<Type> {
262        self.r#type(Type::Week)
263    }
264
265    fn type_time(self) -> Self::Output<Type> {
266        self.r#type(Type::Time)
267    }
268
269    fn type_date_time_local(self) -> Self::Output<Type> {
270        self.r#type(Type::DateTimeLocal)
271    }
272
273    fn type_number(self) -> Self::Output<Type> {
274        self.r#type(Type::Number)
275    }
276
277    fn type_range(self) -> Self::Output<Type> {
278        self.r#type(Type::Range)
279    }
280
281    fn type_color(self) -> Self::Output<Type> {
282        self.r#type(Type::Color)
283    }
284
285    fn type_checkbox(self) -> Self::Output<Type> {
286        self.r#type(Type::Checkbox)
287    }
288
289    fn type_radio(self) -> Self::Output<Type> {
290        self.r#type(Type::Radio)
291    }
292
293    fn type_file(self) -> Self::Output<Type> {
294        self.r#type(Type::File)
295    }
296
297    fn type_submit(self) -> Self::Output<Type> {
298        self.r#type(Type::Submit)
299    }
300
301    fn type_image(self) -> Self::Output<Type> {
302        self.r#type(Type::Image)
303    }
304
305    fn type_reset(self) -> Self::Output<Type> {
306        self.r#type(Type::Reset)
307    }
308
309    fn type_button(self) -> Self::Output<Type> {
310        self.r#type(Type::Button)
311    }
312
313    /// Value of the form control
314    fn value(self, value: impl Into<Cow<'static, str>>) -> Self::Output<Value> {
315        self.with_attribute(Value(value.into()))
316    }
317
318    /// Horizontal dimension.
319    fn width(self, width: u32) -> Self::Output<Width> {
320        self.with_attribute(Width(width))
321    }
322
323    fn on_input<E>(self, event: impl FnOnce(InputEvent) -> E) -> Self::Output<OnInput>
324    where
325        E: ::serde::Serialize + 'static,
326    {
327        let event = event(InputEvent::default());
328        self.with_attribute(OnInput(Box::new(move || {
329            use std::hash::{Hash, Hasher};
330
331            let mut hasher = twox_hash::XxHash32::default();
332            std::any::TypeId::of::<E>().hash(&mut hasher);
333            let hash = hasher.finish() as u32;
334            serde_json::to_string(&event)
335                .map_err(|err| InternalError::Serialize {
336                    what: "on_input event",
337                    err,
338                })
339                .map(|json| (hash, json))
340        })))
341    }
342
343    fn on_change<E>(self, event: impl FnOnce(InputEvent) -> E) -> Self::Output<OnChange>
344    where
345        E: ::serde::Serialize + 'static,
346    {
347        let event = event(InputEvent::default());
348        self.with_attribute(OnChange(Box::new(move || {
349            use std::hash::{Hash, Hasher};
350
351            let mut hasher = twox_hash::XxHash32::default();
352            std::any::TypeId::of::<E>().hash(&mut hasher);
353            let hash = hasher.finish() as u32;
354            serde_json::to_string(&event)
355                .map_err(|err| InternalError::Serialize {
356                    what: "on_change event",
357                    err,
358                })
359                .map(|json| (hash, json))
360        })))
361    }
362}
363
364/// Hint for expected file type in file upload controls.
365#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
366pub struct Accept(pub Cow<'static, str>);
367
368/// Whether the control is checked.
369#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
370pub struct Checked(pub bool);
371
372/// Name of form control to use for sending the element's directionality in form submission.
373#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
374pub struct Dirname(pub Cow<'static, str>);
375
376/// Vertical dimension.
377#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
378pub struct Height(pub u32);
379
380/// List of autocomplete options.
381#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
382pub struct List(pub Cow<'static, str>);
383
384/// Maximum value
385#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
386pub struct Max(pub Cow<'static, str>);
387
388/// Maximum length of value.
389#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
390pub struct MaxLength(pub i32);
391
392/// Minimum value
393#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
394pub struct Min(pub Cow<'static, str>);
395
396/// Minimum length of value
397#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
398pub struct MinLength(pub i32);
399
400/// Whether to allow multiple values.
401#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
402pub struct Multiple(pub bool);
403
404/// Pattern to be matched by the form control's value.
405#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
406pub struct Pattern(pub Cow<'static, str>);
407
408/// User-visible label to be placed within the form control.
409#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
410pub struct Placeholder(pub Cow<'static, str>);
411
412/// Whether to allow the value to be edited by the user
413#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
414pub struct ReadOnly(pub bool);
415
416/// Whether the control is required for form submission
417#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
418pub struct Required(pub bool);
419
420/// Size of the control
421#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
422pub struct Size(pub u32);
423
424/// Granularity to be matched by the form control's value
425#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
426pub struct Step(pub Cow<'static, str>);
427
428/// Value of the form control
429#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
430pub struct Value(pub Cow<'static, str>);
431
432/// Horizontal dimension.
433#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
434pub struct Width(pub u32);
435
436/// Data type of an input element.
437#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
438pub enum Type {
439    Hidden,
440    Text,
441    Search,
442    Tel,
443    Url,
444    Email,
445    Password,
446    Date,
447    Month,
448    Week,
449    Time,
450    DateTimeLocal,
451    Number,
452    Range,
453    Color,
454    Checkbox,
455    Radio,
456    File,
457    Submit,
458    Image,
459    Reset,
460    Button,
461}
462
463impl fmt::Display for Type {
464    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465        f.write_str(match self {
466            Self::Hidden => "hidden",
467            Self::Text => "text",
468            Self::Search => "search",
469            Self::Tel => "tel",
470            Self::Url => "url",
471            Self::Email => "email",
472            Self::Password => "password",
473            Self::Date => "date",
474            Self::Month => "month",
475            Self::Week => "week",
476            Self::Time => "time",
477            Self::DateTimeLocal => "datetime-local",
478            Self::Number => "number",
479            Self::Range => "range",
480            Self::Color => "color",
481            Self::Checkbox => "checkbox",
482            Self::Radio => "radio",
483            Self::File => "file",
484            Self::Submit => "submit",
485            Self::Image => "image",
486            Self::Reset => "reset",
487            Self::Button => "button",
488        })
489    }
490}
491
492pub struct OnInput(pub Box<SerializeEventFn>);
493
494impl Attributes for OnInput {
495    fn render(self, r: &mut crate::render::ElementRenderer) -> Result<(), crate::Error> {
496        // TODO: directly write into el?
497        let (id, payload) = &(self.0)()?;
498        r.attribute("cabin-input", id)
499            .map_err(crate::error::InternalError::from)?;
500        r.attribute("cabin-input-payload", payload)
501            .map_err(crate::error::InternalError::from)?;
502
503        Ok(())
504    }
505}
506
507pub struct OnChange(pub Box<SerializeEventFn>);
508
509impl Attributes for OnChange {
510    fn render(self, r: &mut crate::render::ElementRenderer) -> Result<(), crate::Error> {
511        // TODO: directly write into el?
512        let (id, payload) = &(self.0)()?;
513        r.attribute("cabin-change", id)
514            .map_err(crate::error::InternalError::from)?;
515        r.attribute("cabin-change-payload", payload)
516            .map_err(crate::error::InternalError::from)?;
517
518        Ok(())
519    }
520}