rustact 0.1.0

Async terminal UI framework inspired by React, built on top of ratatui and tokio.
Documentation
use std::fmt;
use std::sync::Arc;

use crate::hooks::Scope;

use super::element::Element;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ComponentId(pub(crate) String);

impl ComponentId {
    pub fn new(path: &[usize], name: &str, key: Option<&str>) -> Self {
        let mut id = path
            .iter()
            .map(|segment| segment.to_string())
            .collect::<Vec<_>>()
            .join(".");
        if let Some(key) = key {
            id.push('#');
            id.push_str(key);
        }
        id.push(':');
        id.push_str(name);
        Self(id)
    }
}

impl fmt::Display for ComponentId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

pub type ComponentFn = Arc<dyn Fn(&mut Scope) -> Element + Send + Sync>;

#[derive(Clone)]
pub struct ComponentElement {
    pub(crate) name: &'static str,
    pub(crate) key: Option<String>,
    pub(crate) render: ComponentFn,
}

impl ComponentElement {
    pub fn new<F>(name: &'static str, render: F) -> Self
    where
        F: Fn(&mut Scope) -> Element + Send + Sync + 'static,
    {
        Self {
            name,
            key: None,
            render: Arc::new(render),
        }
    }

    pub fn key(mut self, key: impl Into<String>) -> Self {
        self.key = Some(key.into());
        self
    }
}

impl From<ComponentElement> for Element {
    fn from(value: ComponentElement) -> Self {
        Element::Component(value)
    }
}

impl fmt::Debug for ComponentElement {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ComponentElement")
            .field("name", &self.name)
            .field("key", &self.key)
            .finish()
    }
}

pub fn component<F>(name: &'static str, render: F) -> ComponentElement
where
    F: Fn(&mut Scope) -> Element + Send + Sync + 'static,
{
    ComponentElement::new(name, render)
}