use std::rc::Rc;
use crate::{Computed, Css, DomDisplay, Value};
#[derive(Clone, Debug)]
pub enum AttrText {
Static(&'static str),
Shared(Rc<String>),
}
impl AttrText {
pub fn as_str(&self) -> &str {
match self {
AttrText::Static(value) => value,
AttrText::Shared(value) => value.as_str(),
}
}
}
impl PartialEq for AttrText {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl From<&'static str> for AttrText {
fn from(value: &'static str) -> Self {
AttrText::Static(value)
}
}
impl From<String> for AttrText {
fn from(value: String) -> Self {
AttrText::Shared(Rc::new(value))
}
}
impl From<Rc<String>> for AttrText {
fn from(value: Rc<String>) -> Self {
AttrText::Shared(value)
}
}
#[derive(Clone)]
pub enum AttrValue {
Static(&'static str),
String(Rc<String>),
Computed(Computed<String>),
ComputedOpt(Computed<Option<String>>),
Value(Value<String>),
ValueOpt(Value<Option<String>>),
}
impl AttrValue {
pub fn get(&self, ctx: &crate::Context) -> Option<AttrText> {
match self {
AttrValue::Static(s) => Some(AttrText::Static(s)),
AttrValue::String(s) => Some(AttrText::Shared(s.clone())),
AttrValue::Computed(c) => Some(AttrText::from(c.get(ctx))),
AttrValue::ComputedOpt(c) => c.get(ctx).map(AttrText::from),
AttrValue::Value(v) => Some(AttrText::from(v.get(ctx))),
AttrValue::ValueOpt(v) => v.get(ctx).map(AttrText::from),
}
}
fn as_static_text(&self) -> Option<&str> {
match self {
AttrValue::Static(value) => Some(value),
AttrValue::String(value) => Some(value.as_str()),
_ => None,
}
}
pub fn combine(classes: Vec<AttrValue>) -> AttrValue {
let all_static = classes.iter().all(|class| class.as_static_text().is_some());
if all_static {
let mut result = Vec::new();
for class in &classes {
let Some(text) = class.as_static_text() else {
continue;
};
let text = text.trim();
if !text.is_empty() {
result.push(text);
}
}
return AttrValue::String(Rc::new(result.join(" ")));
}
let computed = crate::Computed::from(move |ctx| {
let mut result = Vec::new();
for class in &classes {
if let Some(s) = class.get(ctx) {
let s = s.as_str().trim();
if !s.is_empty() {
result.push(s.to_string());
}
}
}
result.join(" ")
});
AttrValue::Computed(computed)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be used as an attribute value",
label = "no conversion from `{Self}` into `AttrValue`",
note = "a value becomes an attribute through an explicit conversion - vertigo no longer converts every `T: ToString`",
note = "a printable type of your own opts in with one line: `impl vertigo::DomDisplay for YourType {{}}`, which covers the value, references to it, its `Computed`/`Value` wrappers, and embedding it as text",
note = "if it comes from another crate, pass `value.to_string()` or wrap it in a newtype of your own"
)]
pub trait IntoAttrValue {
fn into_attr_value(self) -> AttrValue;
}
#[diagnostic::do_not_recommend]
impl<T: Into<AttrValue>> IntoAttrValue for T {
fn into_attr_value(self) -> AttrValue {
self.into()
}
}
impl From<String> for AttrValue {
fn from(value: String) -> Self {
AttrValue::String(Rc::new(value))
}
}
impl From<&&str> for AttrValue {
fn from(value: &&str) -> Self {
AttrValue::from(*value)
}
}
impl From<&str> for AttrValue {
fn from(value: &str) -> Self {
AttrValue::String(Rc::new(value.to_string()))
}
}
impl From<&String> for AttrValue {
fn from(value: &String) -> Self {
AttrValue::String(Rc::new(value.clone()))
}
}
impl From<&&String> for AttrValue {
fn from(value: &&String) -> Self {
AttrValue::from(*value)
}
}
impl From<Rc<String>> for AttrValue {
fn from(value: Rc<String>) -> Self {
AttrValue::String(value)
}
}
impl From<&Rc<String>> for AttrValue {
fn from(value: &Rc<String>) -> Self {
AttrValue::String(value.clone())
}
}
impl From<Rc<str>> for AttrValue {
fn from(value: Rc<str>) -> Self {
AttrValue::String(Rc::new(value.to_string()))
}
}
impl From<&Rc<str>> for AttrValue {
fn from(value: &Rc<str>) -> Self {
AttrValue::String(Rc::new(value.to_string()))
}
}
impl From<std::borrow::Cow<'_, str>> for AttrValue {
fn from(value: std::borrow::Cow<'_, str>) -> Self {
AttrValue::String(Rc::new(value.into_owned()))
}
}
impl From<&std::borrow::Cow<'_, str>> for AttrValue {
fn from(value: &std::borrow::Cow<'_, str>) -> Self {
AttrValue::String(Rc::new(value.to_string()))
}
}
impl<T: DomDisplay> From<T> for AttrValue {
fn from(value: T) -> Self {
AttrValue::String(Rc::new(value.to_string()))
}
}
macro_rules! impl_from_computed_for_attrvalue {
($typename:ty, $variant:ident, |$var:ident| $body:expr) => {
impl From<$typename> for AttrValue {
fn from($var: $typename) -> Self {
AttrValue::$variant($body)
}
}
};
($typename: ty, $variant: ident) => {
impl_from_computed_for_attrvalue!($typename, $variant, |v| v);
};
}
impl_from_computed_for_attrvalue!(Computed<String>, Computed);
impl_from_computed_for_attrvalue!(Computed<Option<String>>, ComputedOpt);
impl_from_computed_for_attrvalue!(&Computed<String>, Computed, |v| v.clone());
impl_from_computed_for_attrvalue!(&Computed<Option<String>>, ComputedOpt, |v| v.clone());
impl_from_computed_for_attrvalue!(Value<String>, Value);
impl_from_computed_for_attrvalue!(Value<Option<String>>, ValueOpt);
impl_from_computed_for_attrvalue!(&Value<String>, Value, |v| v.clone());
impl_from_computed_for_attrvalue!(&Value<Option<String>>, ValueOpt, |v| v.clone());
macro_rules! impl_attr_display_computed {
($typename:ty, |$var:ident| $body:expr) => {
impl<T: DomDisplay + Clone + PartialEq + 'static> From<$typename> for AttrValue {
fn from($var: $typename) -> Self {
AttrValue::Computed($body)
}
}
};
}
impl_attr_display_computed!(Computed<T>, |v| v.map(|v| v.to_string()));
impl_attr_display_computed!(&Computed<T>, |v| v.map(|v| v.to_string()));
impl_attr_display_computed!(Value<T>, |v| v.to_computed().map(|v| v.to_string()));
impl_attr_display_computed!(&Value<T>, |v| v.to_computed().map(|v| v.to_string()));
macro_rules! impl_attr_display_computed_opt {
($typename:ty, |$var:ident| $body:expr) => {
impl<T: DomDisplay + Clone + PartialEq + 'static> From<$typename> for AttrValue {
fn from($var: $typename) -> Self {
AttrValue::ComputedOpt($body)
}
}
};
}
impl_attr_display_computed_opt!(Computed<Option<T>>, |v| v.map(|v| v.map(|v| v.to_string())));
impl_attr_display_computed_opt!(&Computed<Option<T>>, |v| v
.map(|v| v.map(|v| v.to_string())));
impl_attr_display_computed_opt!(Value<Option<T>>, |v| v
.to_computed()
.map(|v| v.map(|v| v.to_string())));
impl_attr_display_computed_opt!(&Value<Option<T>>, |v| v
.to_computed()
.map(|v| v.map(|v| v.to_string())));
pub enum CssAttrValue {
Css(Css),
Computed(Computed<Css>),
}
impl From<Css> for CssAttrValue {
fn from(value: Css) -> Self {
CssAttrValue::Css(value)
}
}
impl From<Computed<Css>> for CssAttrValue {
fn from(value: Computed<Css>) -> Self {
CssAttrValue::Computed(value)
}
}
impl From<&Css> for CssAttrValue {
fn from(value: &Css) -> Self {
CssAttrValue::Css(value.clone())
}
}