thaw_utils 0.1.2

Shared Thaw utility functions
Documentation
use leptos::{
    attr::Attribute,
    context::{Provider, ProviderProps},
    prelude::*,
    tachys::{
        hydration::Cursor,
        renderer::types,
        view::{any_view::AnyViewState, Position, PositionState},
    },
};
use std::collections::HashMap;

#[component]
pub fn SSRMountStyleProvider<Chil>(children: TypedChildren<Chil>) -> impl IntoView
where
    Chil: IntoView + 'static,
{
    let context = SSRMountStyleContext::default();

    let children = Provider(
        ProviderProps::builder()
            .value(context.clone())
            .children(children)
            .build(),
    )
    .into_any();
    SSRMountStyle { context, children }
}

#[derive(Debug, Clone)]
pub struct SSRMountStyleContext {
    styles: ArcStoredValue<HashMap<String, String>>,
}

impl SSRMountStyleContext {
    pub fn use_context() -> Option<Self> {
        use_context()
    }

    pub fn push_style(&self, k: String, v: String) {
        self.styles.write_value().insert(k, v);
    }

    fn default() -> Self {
        Self {
            styles: Default::default(),
        }
    }

    fn html_len(&self) -> usize {
        const TEMPLATE_LEN: usize = r#"<style id=""></style>"#.len();
        let mut html_len = 0;
        let styles = self.styles.write_value();

        styles.iter().for_each(|(k, v)| {
            html_len += k.len() + v.len() + TEMPLATE_LEN;
        });

        html_len
    }

    fn to_html(self) -> String {
        let mut styles = self.styles.write_value();
        styles
            .drain()
            .into_iter()
            .map(|(k, v)| format!(r#"<style id="{k}">{v}</style>"#))
            .collect::<String>()
    }
}

pub struct SSRMountStyle {
    context: SSRMountStyleContext,
    children: AnyView,
}

pub struct SSRMountStyleState {
    state: AnyViewState,
}

impl Render for SSRMountStyle {
    type State = SSRMountStyleState;

    fn build(self) -> Self::State {
        let state = self.children.build();
        SSRMountStyleState { state }
    }

    fn rebuild(self, state: &mut Self::State) {
        self.children.rebuild(&mut state.state);
    }
}

impl AddAnyAttr for SSRMountStyle {
    type Output<SomeNewAttr: Attribute> = Self;

    fn add_any_attr<NewAttr: Attribute>(self, _attr: NewAttr) -> Self::Output<NewAttr>
    where
        Self::Output<NewAttr>: RenderHtml,
    {
        self
    }
}

impl RenderHtml for SSRMountStyle {
    type AsyncOutput = Self;

    const MIN_LENGTH: usize = 0;

    fn html_len(&self) -> usize {
        self.children.html_len() + self.context.html_len()
    }

    fn dry_resolve(&mut self) {
        self.children.dry_resolve();
    }

    async fn resolve(self) -> Self::AsyncOutput {
        self
    }

    fn to_html_with_buf(
        self,
        buf: &mut String,
        position: &mut Position,
        escape: bool,
        mark_branches: bool,
    ) {
        self.children
            .to_html_with_buf(buf, position, escape, mark_branches);

        let head_loc = buf
            .find("<head>")
            .expect("you are using SSRMountStyleProvider without a <head> tag");
        let marker_loc = buf
            .find(r#"<meta name="thaw-ui-style""#)
            .unwrap_or(head_loc + 6);

        buf.insert_str(marker_loc, &self.context.to_html());
    }

    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
        self,
        buf: &mut leptos::tachys::ssr::StreamBuilder,
        position: &mut Position,
        escape: bool,
        mark_branches: bool,
    ) where
        Self: Sized,
    {
        self.children
            .to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape, mark_branches);

        buf.with_buf(|buf| {
            let head_loc = buf
                .find("<head>")
                .expect("you are using SSRMountStyleProvider without a <head> tag");
            let marker_loc = buf
                .find(r#"<meta name="thaw-ui-style""#)
                .unwrap_or(head_loc + 6);
            buf.insert_str(marker_loc, &self.context.to_html());
        });
    }

    fn hydrate<const FROM_SERVER: bool>(
        self,
        cursor: &Cursor,
        position: &PositionState,
    ) -> Self::State {
        let state = self.children.hydrate::<FROM_SERVER>(cursor, position);
        SSRMountStyleState { state }
    }
}

impl Mountable for SSRMountStyleState {
    fn unmount(&mut self) {
        self.state.unmount();
    }

    fn mount(&mut self, parent: &types::Element, marker: Option<&types::Node>) {
        self.state.mount(parent, marker);
    }

    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
        self.state.insert_before_this(child)
    }
}