use crate::{Choice, Curve, ThemeChoice, Tone};
#[allow(unused_imports)]
use crate::{Awaiting, Contrast, Fill, ThemeVariant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FieldKind {
Text,
Secret,
Number,
Range,
Interval,
Email,
Url,
Tel,
Date,
DateTime,
Textarea,
Rich,
Select,
Radio,
Checkbox,
File,
Theme,
Hidden,
}
pub const DATE_FORMAT: &str = "%Y-%m-%d";
pub const DATETIME_FORMAT: &str = "%Y-%m-%dT%H:%M";
impl FieldKind {
#[must_use]
pub const fn temporal(self) -> bool {
matches!(self, Self::Date | Self::DateTime)
}
#[must_use]
pub const fn visible(self) -> bool {
!matches!(self, Self::Hidden)
}
#[must_use]
pub const fn confidential(self) -> bool {
matches!(self, Self::Secret)
}
#[must_use]
pub const fn labels_itself(self) -> bool {
matches!(self, Self::Checkbox)
}
#[must_use]
pub const fn offers_options(self) -> bool {
matches!(self, Self::Select | Self::Radio)
}
#[must_use]
pub const fn offers_themes(self) -> bool {
matches!(self, Self::Theme)
}
#[must_use]
pub const fn multiline(self) -> bool {
matches!(self, Self::Textarea | Self::Rich)
}
#[must_use]
pub const fn takes_files(self) -> bool {
matches!(self, Self::File)
}
#[must_use]
pub const fn measurable(self) -> bool {
matches!(self, Self::Number | Self::Range | Self::Interval)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Family {
Image,
Audio,
Video,
}
impl Family {
#[must_use]
pub const fn wildcard(self) -> &'static str {
match self {
Self::Image => "image/*",
Self::Audio => "audio/*",
Self::Video => "video/*",
}
}
#[must_use]
pub fn of_type(media_type: &str) -> Option<Self> {
let (top, _) = media_type.split_once('/')?;
if top.eq_ignore_ascii_case("image") {
Some(Self::Image)
} else if top.eq_ignore_ascii_case("audio") {
Some(Self::Audio)
} else if top.eq_ignore_ascii_case("video") {
Some(Self::Video)
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Accepted<'a> {
Family(Family),
Type(&'a str),
Suffix(&'a str),
}
impl<'a> Accepted<'a> {
#[must_use]
pub fn family(self) -> Option<Family> {
match self {
Self::Family(family) => Some(family),
Self::Type(media_type) => Family::of_type(media_type),
Self::Suffix(_) => None,
}
}
#[must_use]
pub const fn as_str(self) -> &'a str {
match self {
Self::Family(family) => family.wildcard(),
Self::Type(text) | Self::Suffix(text) => text,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Field<'a> {
pub kind: FieldKind,
pub name: &'a str,
pub upper_name: Option<&'a str>,
pub label: &'a str,
pub hint: Option<&'a str>,
pub error: Option<&'a str>,
pub note: Option<(Tone, &'a str)>,
pub placeholder: Option<&'a str>,
pub options: &'a [Choice<'a>],
pub themes: &'a [ThemeChoice<'a>],
pub follows: Option<Choice<'a>>,
pub accept: &'a [Accepted<'a>],
pub multiple: bool,
pub required: bool,
pub max_length: Option<u32>,
pub min: Option<&'a str>,
pub max: Option<&'a str>,
pub step: Option<&'a str>,
pub curve: Curve<'a>,
pub unit: Option<&'a str>,
pub extended: bool,
pub as_instant: bool,
}
impl<'a> Field<'a> {
#[must_use]
pub const fn new(kind: FieldKind, name: &'a str, label: &'a str) -> Self {
Self {
kind,
name,
upper_name: None,
label,
hint: None,
error: None,
note: None,
placeholder: None,
options: &[],
themes: &[],
follows: None,
accept: &[],
multiple: false,
required: false,
max_length: None,
min: None,
max: None,
step: None,
curve: Curve::Linear { step: None },
unit: None,
extended: false,
as_instant: false,
}
}
#[must_use]
pub const fn range(name: &'a str, label: &'a str, min: &'a str, max: &'a str) -> Self {
Self {
min: Some(min),
max: Some(max),
..Self::new(FieldKind::Range, name, label)
}
}
#[must_use]
pub const fn interval(name: &'a str, upper_name: &'a str, label: &'a str) -> Self {
Self {
upper_name: Some(upper_name),
..Self::new(FieldKind::Interval, name, label)
}
}
#[must_use]
pub const fn upload(name: &'a str, label: &'a str, accept: &'a [Accepted<'a>]) -> Self {
Self {
accept,
..Self::new(FieldKind::File, name, label)
}
}
#[must_use]
pub const fn select(name: &'a str, label: &'a str, options: &'a [Choice<'a>]) -> Self {
Self::offering(FieldKind::Select, name, label, options)
}
#[must_use]
pub const fn radio(name: &'a str, label: &'a str, options: &'a [Choice<'a>]) -> Self {
Self::offering(FieldKind::Radio, name, label, options)
}
#[must_use]
pub const fn theme(name: &'a str, label: &'a str, themes: &'a [ThemeChoice<'a>]) -> Self {
Self {
themes,
..Self::new(FieldKind::Theme, name, label)
}
}
#[must_use]
pub const fn following(mut self, follow: Choice<'a>) -> Self {
self.follows = Some(follow);
self
}
const fn offering(
kind: FieldKind,
name: &'a str,
label: &'a str,
options: &'a [Choice<'a>],
) -> Self {
Self {
options,
..Self::new(kind, name, label)
}
}
#[must_use]
pub const fn invalid(&self) -> bool {
self.error.is_some()
}
#[must_use]
pub const fn bounded(&self) -> bool {
self.min.is_some() && self.max.is_some()
}
#[must_use]
pub fn accepts_media(&self) -> bool {
self.accept.iter().any(|one| one.family().is_some())
}
}