use alloc::rc::Rc;
use alloc::string::String;
use alloc::vec::Vec;
use core::cell::RefCell;
use crate::line::{reactive_graph, tachys};
use reactive_graph::graph::untrack;
use tachys::html::attribute::any_attribute::AnyAttribute;
use tachys::html::attribute::{Attribute, AttributeValue};
use tachys::html::property::IntoProperty;
use tachys::hydration::Cursor;
#[cfg(not(feature = "mark-fallback-lang"))]
use tachys::renderer::CastFrom;
use tachys::renderer::Rndr;
use tachys::renderer::types::{Element, Text as TextNode};
use tachys::view::add_attr::AddAnyAttr;
use tachys::view::any_view::AnyViewState;
use tachys::view::iterators::VecState;
#[cfg(not(all(feature = "leptos-0-8", not(feature = "leptos"))))]
use tachys::view::RenderFlags;
use tachys::view::{Mountable, Position, PositionState, Render, RenderHtml, ToTemplate};
#[cfg(feature = "mark-fallback-lang")]
use crate::leptos::lang::{self, Wrapper};
#[cfg(any(
not(feature = "static-locale"),
all(feature = "hydrate", feature = "fn-datetime")
))]
use crate::leptos::registry::Relocalize;
use crate::leptos::registry::{self, Target};
use crate::leptos::rich;
use crate::leptos::state::{self, TextUse};
use crate::leptos::text::{self, Description};
use crate::{Tr, TrArgs, TrDyn, TrRich};
pub struct TrState {
slot: u32,
node: TextNode,
#[cfg(feature = "mark-fallback-lang")]
wrapper: Wrapper,
}
impl core::fmt::Debug for TrState {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("TrState { slot: ")?;
crate::debug::uint(f, u64::from(self.slot))?;
f.write_str(" }")
}
}
impl Drop for TrState {
fn drop(&mut self) {
registry::remove(self.slot);
}
}
impl Mountable for TrState {
fn unmount(&mut self) {
#[cfg(feature = "mark-fallback-lang")]
if let Some(mut wrapper) = self.wrapper.get() {
wrapper.unmount();
return;
}
self.node.unmount();
}
fn mount(&mut self, parent: &Element, marker: Option<&tachys::renderer::types::Node>) {
#[cfg(feature = "mark-fallback-lang")]
Rndr::insert_node(parent, &self.wrapper.mount_into(parent, &self.node), marker);
#[cfg(not(feature = "mark-fallback-lang"))]
Rndr::insert_node(parent, self.node.as_ref(), marker);
}
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
#[cfg(feature = "mark-fallback-lang")]
if let Some(wrapper) = self.wrapper.get() {
return wrapper.insert_before_this(child);
}
self.node.insert_before_this(child)
}
fn elements(&self) -> Vec<Element> {
#[cfg(feature = "mark-fallback-lang")]
if let Some(wrapper) = self.wrapper.get() {
return alloc::vec![wrapper];
}
Vec::new()
}
}
fn with_text<D: Description, R>(description: &D, use_: TextUse, body: impl FnOnce(&str) -> R) -> R {
untrack(|| text::with_active_text(description, use_, body))
}
#[cfg(feature = "mark-fallback-lang")]
fn with_marked_text<D: Description, R>(
description: &D,
body: impl FnOnce(&str, Option<lang::Lender<'_>>) -> R,
) -> R {
untrack(|| text::with_active_marked_text(description, body))
}
#[cfg(not(feature = "mark-fallback-lang"))]
fn adopt_text(cursor: &Cursor, position: &PositionState, text: &str) -> TextNode {
let node = walk(cursor, position);
if let Some(text_node) = TextNode::cast_from(node.clone()) {
return text_node;
}
hydration_mismatch(&node);
Rndr::create_text_node(text)
}
fn walk(cursor: &Cursor, position: &PositionState) -> tachys::renderer::types::Node {
if position.get() == Position::FirstChild {
cursor.child();
} else {
cursor.sibling();
}
if matches!(position.get(), Position::NextChildAfterText) {
cursor.sibling();
}
position.set(Position::NextChildAfterText);
cursor.current()
}
#[cfg(feature = "mark-fallback-lang")]
fn adopt_marked(
cursor: &Cursor,
position: &PositionState,
text: &str,
wrapper: &Wrapper,
) -> TextNode {
let node = walk(cursor, position);
if let Some(text_node) = wrapper.adopt(node.clone()) {
return text_node;
}
hydration_mismatch(&node);
Rndr::create_text_node(text)
}
#[cfg(feature = "mark-fallback-lang")]
fn build_marked<D: Description>(description: D) -> TrState {
let wrapper = Wrapper::default();
let node = with_marked_text(&description, |text, lender| {
let node = Rndr::create_text_node(text);
wrapper.fit(&node, lender);
node
});
let slot = registry::insert(
Target::Text(node.clone(), wrapper.clone()),
description.into_stored(),
);
TrState {
slot,
node,
wrapper,
}
}
#[cfg(feature = "mark-fallback-lang")]
fn hydrate_marked<const FROM_SERVER: bool, D: Description>(
description: D,
cursor: &Cursor,
position: &PositionState,
) -> TrState {
let wrapper = Wrapper::default();
let node = if FROM_SERVER {
adopt_marked(cursor, position, "", &wrapper)
} else {
with_marked_text(&description, |text, lender| {
let node = adopt_marked(cursor, position, text, &wrapper);
Rndr::set_text(&node, text);
wrapper.fit(&node, lender);
node
})
};
let slot = registry::insert_hydrated(
Target::Text(node.clone(), wrapper.clone()),
description.into_stored(),
);
TrState {
slot,
node,
wrapper,
}
}
#[cfg(feature = "mark-fallback-lang")]
fn html_marked<D: Description>(
description: &D,
buf: &mut String,
position: &mut Position,
separator: bool,
write: impl FnOnce(&str, &mut String, &mut Position),
) {
let text_only = matches!(*position, Position::FirstChild)
&& lang::open_tag(buf).is_some_and(lang::is_text_only);
with_marked_text(description, |text, lender| match lender {
None => write(text, buf, position),
Some(_) if text_only => write(text, buf, position),
Some(lender) => {
if separator {
buf.push_str("<!>");
}
lang::write_open(buf, lender);
write(text, buf, &mut Position::FirstChild);
buf.push_str("</span>");
*position = Position::NextChildAfterText;
}
});
}
fn hydration_mismatch(node: &tachys::renderer::types::Node) {
#[cfg(any(feature = "hydrate", feature = "csr"))]
web_sys::console::error_2(
&wasm_bindgen::JsValue::from_str(
"mf2: expected a text node while hydrating a translated message, but found this. \
The message will not be interactive; the rest of the page still hydrates.",
),
node.as_ref(),
);
#[cfg(not(any(feature = "hydrate", feature = "csr")))]
let _ = node;
}
macro_rules! render_description {
($ty:ty) => {
impl AddAnyAttr for $ty {
type Output<SomeNewAttr: Attribute> = $ty;
fn add_any_attr<NewAttr: Attribute>(self, _attr: NewAttr) -> Self::Output<NewAttr> {
self
}
}
impl Render for $ty {
type State = TrState;
#[cfg(not(feature = "mark-fallback-lang"))]
fn build(self) -> Self::State {
let node = with_text(&self, TextUse::Displayed, |text| {
Rndr::create_text_node(text)
});
let slot = registry::insert(Target::Text(node.clone()), self.into_stored());
TrState { slot, node }
}
#[cfg(feature = "mark-fallback-lang")]
fn build(self) -> Self::State {
build_marked(self)
}
fn rebuild(self, state: &mut Self::State) {
let node = &state.node;
#[cfg(feature = "mark-fallback-lang")]
let target = || Some(Target::Text(node.clone(), state.wrapper.clone()));
#[cfg(not(feature = "mark-fallback-lang"))]
let target = || Some(Target::Text(node.clone()));
registry::replace(&mut state.slot, target, self.into_stored());
}
}
impl RenderHtml for $ty {
type AsyncOutput = Self;
type Owned = Self;
const MIN_LENGTH: usize = 0;
fn dry_resolve(&mut self) {}
fn resolve(self) -> impl core::future::Future<Output = Self::AsyncOutput> + Send {
core::future::ready(self)
}
fn html_len(&self) -> usize {
0
}
#[cfg(all(feature = "leptos-0-8", not(feature = "leptos")))]
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
#[cfg(feature = "mark-fallback-lang")]
html_marked(
&self,
buf,
position,
matches!(*position, Position::NextChildAfterText),
|text, buf, position| {
<&str as RenderHtml>::to_html_with_buf(
text,
buf,
position,
escape,
mark_branches,
extra_attrs,
);
},
);
#[cfg(not(feature = "mark-fallback-lang"))]
with_text(&self, TextUse::Displayed, |text| {
<&str as RenderHtml>::to_html_with_buf(
text,
buf,
position,
escape,
mark_branches,
extra_attrs,
);
});
}
#[cfg(not(all(feature = "leptos-0-8", not(feature = "leptos"))))]
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
flags: RenderFlags,
extra_attrs: Vec<AnyAttribute>,
) {
#[cfg(feature = "mark-fallback-lang")]
html_marked(
&self,
buf,
position,
flags.hydrate && matches!(*position, Position::NextChildAfterText),
|text, buf, position| {
<&str as RenderHtml>::to_html_with_buf(
text,
buf,
position,
flags,
extra_attrs,
);
},
);
#[cfg(not(feature = "mark-fallback-lang"))]
with_text(&self, TextUse::Displayed, |text| {
<&str as RenderHtml>::to_html_with_buf(text, buf, position, flags, extra_attrs);
});
}
#[cfg(feature = "mark-fallback-lang")]
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
hydrate_marked::<FROM_SERVER, _>(self, cursor, position)
}
#[cfg(not(feature = "mark-fallback-lang"))]
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let node = if FROM_SERVER {
adopt_text(cursor, position, "")
} else {
with_text(&self, TextUse::Displayed, |text| {
let node = adopt_text(cursor, position, text);
Rndr::set_text(&node, text);
node
})
};
let slot =
registry::insert_hydrated(Target::Text(node.clone()), self.into_stored());
TrState { slot, node }
}
fn into_owned(self) -> Self::Owned {
self
}
}
impl ToTemplate for $ty {
const TEMPLATE: &'static str = " <!>";
fn to_template(
buf: &mut String,
_class: &mut String,
_style: &mut String,
_inner_html: &mut String,
position: &mut Position,
) {
<&str as ToTemplate>::to_template(buf, _class, _style, _inner_html, position);
}
}
};
}
render_description!(Tr);
render_description!(TrArgs);
render_description!(TrDyn);
pub struct TrAttrState {
slot: u32,
#[cfg(feature = "static-locale")]
el: Element,
}
impl core::fmt::Debug for TrAttrState {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("TrAttrState { slot: ")?;
crate::debug::uint(f, u64::from(self.slot))?;
f.write_str(" }")
}
}
impl TrAttrState {
#[cfg_attr(not(feature = "static-locale"), allow(unused_variables))]
fn new(slot: u32, el: &Element) -> TrAttrState {
TrAttrState {
slot,
#[cfg(feature = "static-locale")]
el: el.clone(),
}
}
#[cfg_attr(not(feature = "static-locale"), allow(unused_variables))]
fn rebuild(
&mut self,
target: impl FnOnce(&Element) -> Target,
desc: crate::leptos::text::Stored,
) {
#[cfg(feature = "static-locale")]
let el = &self.el;
#[cfg(feature = "static-locale")]
registry::replace(&mut self.slot, || Some(target(el)), desc);
#[cfg(not(feature = "static-locale"))]
registry::replace(&mut self.slot, || None, desc);
}
}
impl Drop for TrAttrState {
fn drop(&mut self) {
registry::remove(self.slot);
}
}
macro_rules! attribute_description {
($ty:ty) => {
impl AttributeValue for $ty {
type State = TrAttrState;
type AsyncOutput = Self;
type Cloneable = Self;
type CloneableOwned = Self;
fn html_len(&self) -> usize {
0
}
fn to_html(self, key: &str, buf: &mut String) {
with_text(&self, state::attribute_use(key), |text| {
<&str as AttributeValue>::to_html(text, key, buf);
});
}
fn to_template(_key: &str, _buf: &mut String) {}
fn hydrate<const FROM_SERVER: bool>(self, key: &str, el: &Element) -> Self::State {
if !FROM_SERVER {
with_text(&self, state::attribute_use(key), |text| {
Rndr::set_attribute(el, key, text);
});
}
let slot = registry::insert_hydrated(
Target::Attribute(el.clone(), key.into()),
self.into_stored(),
);
TrAttrState::new(slot, el)
}
fn build(self, el: &Element, key: &str) -> Self::State {
with_text(&self, state::attribute_use(key), |text| {
Rndr::set_attribute(el, key, text);
});
let slot = registry::insert(
Target::Attribute(el.clone(), key.into()),
self.into_stored(),
);
TrAttrState::new(slot, el)
}
fn rebuild(self, key: &str, state: &mut Self::State) {
state.rebuild(
|el| Target::Attribute(el.clone(), key.into()),
self.into_stored(),
);
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self
}
fn dry_resolve(&mut self) {}
fn resolve(self) -> impl core::future::Future<Output = Self::AsyncOutput> + Send {
core::future::ready(self)
}
}
impl IntoProperty for $ty {
type State = TrAttrState;
type Cloneable = Self;
type CloneableOwned = Self;
#[cfg(not(all(feature = "hydrate", feature = "fn-datetime")))]
fn hydrate<const FROM_SERVER: bool>(self, el: &Element, key: &str) -> Self::State {
let _ = FROM_SERVER;
IntoProperty::build(self, el, key)
}
#[cfg(all(feature = "hydrate", feature = "fn-datetime"))]
fn hydrate<const FROM_SERVER: bool>(self, el: &Element, key: &str) -> Self::State {
let _ = FROM_SERVER;
with_text(&self, TextUse::Plain, |text| {
Rndr::set_property_or_value(el, key, &wasm_bindgen::JsValue::from_str(text));
});
let slot = registry::insert_hydrated(
Target::Property(el.clone(), key.into()),
self.into_stored(),
);
TrAttrState::new(slot, el)
}
fn build(self, el: &Element, key: &str) -> Self::State {
with_text(&self, TextUse::Plain, |text| {
Rndr::set_property_or_value(el, key, &wasm_bindgen::JsValue::from_str(text));
});
let slot =
registry::insert(Target::Property(el.clone(), key.into()), self.into_stored());
TrAttrState::new(slot, el)
}
fn rebuild(self, state: &mut Self::State, key: &str) {
state.rebuild(
|el| Target::Property(el.clone(), key.into()),
self.into_stored(),
);
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self
}
}
};
}
attribute_description!(Tr);
attribute_description!(TrArgs);
attribute_description!(TrRich);
attribute_description!(TrDyn);
pub(crate) struct RichNode {
desc: TrRich,
state: VecState<AnyViewState>,
}
#[cfg(any(
not(feature = "static-locale"),
all(feature = "hydrate", feature = "fn-datetime")
))]
impl Relocalize for RichNode {
fn relocalize(&mut self, catalog: &mf2_catalog::Catalog) {
rich::fragment(&self.desc, catalog).rebuild(&mut self.state);
}
}
pub struct TrRichState {
node: Rc<RefCell<RichNode>>,
slot: u32,
}
impl core::fmt::Debug for TrRichState {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("TrRichState { slot: ")?;
crate::debug::uint(f, u64::from(self.slot))?;
f.write_str(" }")
}
}
impl TrRichState {
fn new(desc: TrRich, state: VecState<AnyViewState>) -> TrRichState {
let node = Rc::new(RefCell::new(RichNode { desc, state }));
#[cfg(not(feature = "static-locale"))]
let slot = {
let erased: Rc<RefCell<dyn Relocalize>> = node.clone();
registry::insert_rich(erased)
};
#[cfg(feature = "static-locale")]
let slot = registry::NONE;
TrRichState { node, slot }
}
fn with<R>(&self, body: impl FnOnce(&mut VecState<AnyViewState>) -> R) -> Option<R> {
let mut node = self.node.try_borrow_mut().ok()?;
Some(body(&mut node.state))
}
}
impl Drop for TrRichState {
fn drop(&mut self) {
registry::remove(self.slot);
}
}
impl Mountable for TrRichState {
fn unmount(&mut self) {
self.with(Mountable::unmount);
}
fn mount(&mut self, parent: &Element, marker: Option<&tachys::renderer::types::Node>) {
self.with(|state| state.mount(parent, marker));
}
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
self.with(|state| state.insert_before_this(child))
.unwrap_or(false)
}
fn elements(&self) -> Vec<Element> {
self.with(|state| state.elements()).unwrap_or_default()
}
}
impl AddAnyAttr for TrRich {
type Output<SomeNewAttr: Attribute> = TrRich;
fn add_any_attr<NewAttr: Attribute>(self, _attr: NewAttr) -> Self::Output<NewAttr> {
self
}
}
impl Render for TrRich {
type State = TrRichState;
fn build(self) -> Self::State {
let state = rich::active_fragment(&self).build();
TrRichState::new(self, state)
}
fn rebuild(self, state: &mut Self::State) {
let fragment = rich::active_fragment(&self);
if let Ok(mut node) = state.node.try_borrow_mut() {
node.desc = self;
fragment.rebuild(&mut node.state);
}
}
}
impl RenderHtml for TrRich {
type AsyncOutput = Self;
type Owned = Self;
const MIN_LENGTH: usize = 0;
fn dry_resolve(&mut self) {}
fn resolve(self) -> impl core::future::Future<Output = Self::AsyncOutput> + Send {
core::future::ready(self)
}
fn html_len(&self) -> usize {
0
}
#[cfg(all(feature = "leptos-0-8", not(feature = "leptos")))]
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
rich::active_fragment(&self).to_html_with_buf(
buf,
position,
escape,
mark_branches,
extra_attrs,
);
}
#[cfg(not(all(feature = "leptos-0-8", not(feature = "leptos"))))]
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
flags: RenderFlags,
extra_attrs: Vec<AnyAttribute>,
) {
rich::active_fragment(&self).to_html_with_buf(buf, position, flags, extra_attrs);
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
#[cfg(all(feature = "hydrate", feature = "fn-datetime"))]
let fragment = crate::leptos::zone::scoped(crate::leptos::zone::hydrating_zone(), || {
rich::active_fragment(&self)
});
#[cfg(not(all(feature = "hydrate", feature = "fn-datetime")))]
let fragment = rich::active_fragment(&self);
let state = TrRichState::new(self, fragment.hydrate::<FROM_SERVER>(cursor, position));
#[cfg(all(feature = "hydrate", feature = "fn-datetime"))]
crate::leptos::zone::hydrated_rich(state.node.clone());
state
}
fn into_owned(self) -> Self::Owned {
self
}
}