mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
perf: optimize inert HTML elements (#2989)
This commit is contained in:
parent
ba9604101d
commit
5af7b54c9c
10 changed files with 473 additions and 26 deletions
|
@ -162,22 +162,24 @@ pub fn App() -> impl IntoView {
|
|||
<table class="table table-hover table-striped test-data">
|
||||
<tbody>
|
||||
<For
|
||||
each={move || data.get()}
|
||||
key={|row| row.id}
|
||||
each=move || data.get()
|
||||
key=|row| row.id
|
||||
children=move |row: RowData| {
|
||||
let row_id = row.id;
|
||||
let label = row.label;
|
||||
let is_selected = is_selected.clone();
|
||||
ViewTemplate::new(view! {
|
||||
<tr class:danger={move || is_selected.selected(Some(row_id))}>
|
||||
<td class="col-md-1">{row_id.to_string()}</td>
|
||||
<td class="col-md-4"><a on:click=move |_| set_selected.set(Some(row_id))>{move || label.get()}</a></td>
|
||||
<td class="col-md-1"><a on:click=move |_| remove(row_id)><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a></td>
|
||||
<td class="col-md-6"/>
|
||||
</tr>
|
||||
})
|
||||
template! {
|
||||
< tr class : danger = { move || is_selected.selected(Some(row_id)) }
|
||||
> < td class = "col-md-1" > { row_id.to_string() } </ td > < td
|
||||
class = "col-md-4" >< a on : click = move | _ | set_selected
|
||||
.set(Some(row_id)) > { move || label.get() } </ a ></ td > < td
|
||||
class = "col-md-1" >< a on : click = move | _ | remove(row_id) ><
|
||||
span class = "glyphicon glyphicon-remove" aria - hidden = "true" ></
|
||||
span ></ a ></ td > < td class = "col-md-6" /> </ tr >
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true"></span>
|
||||
|
|
|
@ -266,6 +266,21 @@ mod slot;
|
|||
#[proc_macro]
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
pub fn view(tokens: TokenStream) -> TokenStream {
|
||||
view_macro_impl(tokens, false)
|
||||
}
|
||||
|
||||
/// The `template` macro behaves like [`view`], except that it wraps the entire tree in a
|
||||
/// [`ViewTemplate`](leptos::prelude::ViewTemplate). This optimizes creation speed by rendering
|
||||
/// most of the view into a `<template>` tag with HTML rendered at compile time, then hydrating it.
|
||||
/// In exchange, there is a small binary size overhead.
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro]
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
pub fn template(tokens: TokenStream) -> TokenStream {
|
||||
view_macro_impl(tokens, true)
|
||||
}
|
||||
|
||||
fn view_macro_impl(tokens: TokenStream, template: bool) -> TokenStream {
|
||||
let tokens: proc_macro2::TokenStream = tokens.into();
|
||||
let mut tokens = tokens.into_iter();
|
||||
|
||||
|
@ -308,12 +323,13 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
&mut nodes,
|
||||
global_class.as_ref(),
|
||||
normalized_call_site(proc_macro::Span::call_site()),
|
||||
template,
|
||||
);
|
||||
|
||||
// The allow lint needs to be put here instead of at the expansion of
|
||||
// view::attribute_value(). Adding this next to the expanded expression
|
||||
// seems to break rust-analyzer, but it works when the allow is put here.
|
||||
quote! {
|
||||
let output = quote! {
|
||||
{
|
||||
#[allow(unused_braces)]
|
||||
{
|
||||
|
@ -321,6 +337,14 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
#nodes_output
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if template {
|
||||
quote! {
|
||||
::leptos::prelude::ViewTemplate::new(#output)
|
||||
}
|
||||
} else {
|
||||
output
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use syn::{spanned::Spanned, Expr, ExprPath, ExprRange, RangeLimits, Stmt};
|
|||
pub(crate) fn component_to_tokens(
|
||||
node: &mut NodeElement<impl CustomNode>,
|
||||
global_class: Option<&TokenTree>,
|
||||
disable_inert_html: bool,
|
||||
) -> TokenStream {
|
||||
#[allow(unused)] // TODO this is used by hot-reloading
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -191,6 +192,7 @@ pub(crate) fn component_to_tokens(
|
|||
Some(&mut slots),
|
||||
global_class,
|
||||
None,
|
||||
disable_inert_html,
|
||||
);
|
||||
|
||||
// TODO view marker for hot-reloading
|
||||
|
|
|
@ -15,7 +15,7 @@ use rstml::node::{
|
|||
};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{HashMap, HashSet},
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
};
|
||||
use syn::{
|
||||
spanned::Spanned, Expr, Expr::Tuple, ExprLit, ExprRange, Lit, LitStr,
|
||||
|
@ -34,6 +34,7 @@ pub fn render_view(
|
|||
nodes: &mut [Node],
|
||||
global_class: Option<&TokenTree>,
|
||||
view_marker: Option<String>,
|
||||
disable_inert_html: bool,
|
||||
) -> Option<TokenStream> {
|
||||
let (base, should_add_view) = match nodes.len() {
|
||||
0 => {
|
||||
|
@ -52,6 +53,8 @@ pub fn render_view(
|
|||
None,
|
||||
global_class,
|
||||
view_marker.as_deref(),
|
||||
true,
|
||||
disable_inert_html,
|
||||
),
|
||||
// only add View wrapper and view marker to a regular HTML
|
||||
// element or component, not to a <{..} /> attribute list
|
||||
|
@ -67,6 +70,7 @@ pub fn render_view(
|
|||
None,
|
||||
global_class,
|
||||
view_marker.as_deref(),
|
||||
disable_inert_html,
|
||||
),
|
||||
true,
|
||||
),
|
||||
|
@ -91,12 +95,281 @@ pub fn render_view(
|
|||
})
|
||||
}
|
||||
|
||||
fn is_inert_element(orig_node: &Node<impl CustomNode>) -> bool {
|
||||
// do not use this if the top-level node is not an Element,
|
||||
// or if it's an element with no children and no attrs
|
||||
match orig_node {
|
||||
Node::Element(el) => {
|
||||
if el.attributes().is_empty() && el.children.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
// otherwise, walk over all the nodes to make sure everything is inert
|
||||
let mut nodes = VecDeque::from([orig_node]);
|
||||
|
||||
while let Some(current_element) = nodes.pop_front() {
|
||||
match current_element {
|
||||
Node::Text(_) | Node::RawText(_) => {}
|
||||
Node::Element(node) => {
|
||||
if is_component_node(node) {
|
||||
return false;
|
||||
}
|
||||
if is_spread_marker(node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match node.name() {
|
||||
NodeName::Block(_) => return false,
|
||||
_ => {
|
||||
// check all attributes
|
||||
for attr in node.attributes() {
|
||||
match attr {
|
||||
NodeAttribute::Block(_) => return false,
|
||||
NodeAttribute::Attribute(attr) => {
|
||||
let static_key =
|
||||
!matches!(attr.key, NodeName::Block(_));
|
||||
|
||||
let static_value = match attr
|
||||
.possible_value
|
||||
.to_value()
|
||||
{
|
||||
None => true,
|
||||
Some(value) => {
|
||||
matches!(&value.value, KVAttributeValue::Expr(expr) if {
|
||||
if let Expr::Lit(lit) = expr {
|
||||
matches!(&lit.lit, Lit::Str(_))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
if !static_key || !static_value {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check all children
|
||||
nodes.extend(&node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
enum Item<'a, T> {
|
||||
Node(&'a Node<T>),
|
||||
ClosingTag(String),
|
||||
}
|
||||
|
||||
enum InertElementBuilder<'a> {
|
||||
GlobalClass {
|
||||
global_class: &'a TokenTree,
|
||||
strs: Vec<GlobalClassItem<'a>>,
|
||||
buffer: String,
|
||||
},
|
||||
NoGlobalClass {
|
||||
buffer: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> ToTokens for InertElementBuilder<'a> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
InertElementBuilder::GlobalClass { strs, .. } => {
|
||||
tokens.extend(quote! {
|
||||
[#(#strs),*].join("")
|
||||
});
|
||||
}
|
||||
InertElementBuilder::NoGlobalClass { buffer } => {
|
||||
tokens.extend(quote! {
|
||||
#buffer
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum GlobalClassItem<'a> {
|
||||
Global(&'a TokenTree),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl<'a> ToTokens for GlobalClassItem<'a> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let addl_tokens = match self {
|
||||
GlobalClassItem::Global(v) => v.to_token_stream(),
|
||||
GlobalClassItem::String(v) => v.to_token_stream(),
|
||||
};
|
||||
tokens.extend(addl_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InertElementBuilder<'a> {
|
||||
fn new(global_class: Option<&'a TokenTree>) -> Self {
|
||||
match global_class {
|
||||
None => Self::NoGlobalClass {
|
||||
buffer: String::new(),
|
||||
},
|
||||
Some(global_class) => Self::GlobalClass {
|
||||
global_class,
|
||||
strs: Vec::new(),
|
||||
buffer: String::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, c: char) {
|
||||
match self {
|
||||
InertElementBuilder::GlobalClass { buffer, .. } => buffer.push(c),
|
||||
InertElementBuilder::NoGlobalClass { buffer } => buffer.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_str(&mut self, s: &str) {
|
||||
match self {
|
||||
InertElementBuilder::GlobalClass { buffer, .. } => {
|
||||
buffer.push_str(s)
|
||||
}
|
||||
InertElementBuilder::NoGlobalClass { buffer } => buffer.push_str(s),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_class(&mut self, class: &str) {
|
||||
match self {
|
||||
InertElementBuilder::GlobalClass {
|
||||
global_class,
|
||||
strs,
|
||||
buffer,
|
||||
} => {
|
||||
buffer.push_str(" class=\"");
|
||||
strs.push(GlobalClassItem::String(std::mem::take(buffer)));
|
||||
strs.push(GlobalClassItem::Global(global_class));
|
||||
buffer.push(' ');
|
||||
buffer.push_str(class);
|
||||
buffer.push('"');
|
||||
}
|
||||
InertElementBuilder::NoGlobalClass { buffer } => {
|
||||
buffer.push_str(" class=\"");
|
||||
buffer.push_str(class);
|
||||
buffer.push('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&mut self) {
|
||||
match self {
|
||||
InertElementBuilder::GlobalClass { strs, buffer, .. } => {
|
||||
strs.push(GlobalClassItem::String(std::mem::take(buffer)));
|
||||
}
|
||||
InertElementBuilder::NoGlobalClass { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inert_element_to_tokens(
|
||||
node: &Node<impl CustomNode>,
|
||||
global_class: Option<&TokenTree>,
|
||||
) -> Option<TokenStream> {
|
||||
let mut html = InertElementBuilder::new(global_class);
|
||||
let mut nodes = VecDeque::from([Item::Node(node)]);
|
||||
|
||||
while let Some(current) = nodes.pop_front() {
|
||||
match current {
|
||||
Item::ClosingTag(tag) => {
|
||||
// closing tag
|
||||
html.push_str("</");
|
||||
html.push_str(&tag);
|
||||
html.push('>');
|
||||
}
|
||||
Item::Node(current) => {
|
||||
match current {
|
||||
Node::RawText(raw) => {
|
||||
let text = raw.to_string_best();
|
||||
html.push_str(&text);
|
||||
}
|
||||
Node::Text(text) => {
|
||||
let text = text.value_string();
|
||||
html.push_str(&text);
|
||||
}
|
||||
Node::Element(node) => {
|
||||
let self_closing = is_self_closing(node);
|
||||
let el_name = node.name().to_string();
|
||||
|
||||
// opening tag
|
||||
html.push('<');
|
||||
html.push_str(&el_name);
|
||||
|
||||
for attr in node.attributes() {
|
||||
if let NodeAttribute::Attribute(attr) = attr {
|
||||
let attr_name = attr.key.to_string();
|
||||
if attr_name != "class" {
|
||||
html.push(' ');
|
||||
html.push_str(&attr_name);
|
||||
}
|
||||
|
||||
if let Some(value) =
|
||||
attr.possible_value.to_value()
|
||||
{
|
||||
if let KVAttributeValue::Expr(Expr::Lit(
|
||||
lit,
|
||||
)) = &value.value
|
||||
{
|
||||
if let Lit::Str(txt) = &lit.lit {
|
||||
if attr_name == "class" {
|
||||
html.push_class(&txt.value());
|
||||
} else {
|
||||
html.push_str("=\"");
|
||||
html.push_str(&txt.value());
|
||||
html.push('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
html.push('>');
|
||||
|
||||
// render all children
|
||||
if !self_closing {
|
||||
nodes.push_front(Item::ClosingTag(el_name));
|
||||
let children = node.children.iter().rev();
|
||||
for child in children {
|
||||
nodes.push_front(Item::Node(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.finish();
|
||||
|
||||
Some(quote! {
|
||||
::leptos::tachys::html::InertElement::new(#html)
|
||||
})
|
||||
}
|
||||
|
||||
fn element_children_to_tokens(
|
||||
nodes: &mut [Node<impl CustomNode>],
|
||||
parent_type: TagType,
|
||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||
global_class: Option<&TokenTree>,
|
||||
view_marker: Option<&str>,
|
||||
disable_inert_html: bool,
|
||||
) -> Option<TokenStream> {
|
||||
let children = children_to_tokens(
|
||||
nodes,
|
||||
|
@ -104,6 +377,8 @@ fn element_children_to_tokens(
|
|||
parent_slots,
|
||||
global_class,
|
||||
view_marker,
|
||||
false,
|
||||
disable_inert_html,
|
||||
);
|
||||
if children.is_empty() {
|
||||
None
|
||||
|
@ -145,6 +420,7 @@ fn fragment_to_tokens(
|
|||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||
global_class: Option<&TokenTree>,
|
||||
view_marker: Option<&str>,
|
||||
disable_inert_html: bool,
|
||||
) -> Option<TokenStream> {
|
||||
let children = children_to_tokens(
|
||||
nodes,
|
||||
|
@ -152,6 +428,8 @@ fn fragment_to_tokens(
|
|||
parent_slots,
|
||||
global_class,
|
||||
view_marker,
|
||||
true,
|
||||
disable_inert_html,
|
||||
);
|
||||
if children.is_empty() {
|
||||
None
|
||||
|
@ -183,6 +461,8 @@ fn children_to_tokens(
|
|||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||
global_class: Option<&TokenTree>,
|
||||
view_marker: Option<&str>,
|
||||
top_level: bool,
|
||||
disable_inert_html: bool,
|
||||
) -> Vec<TokenStream> {
|
||||
if nodes.len() == 1 {
|
||||
match node_to_tokens(
|
||||
|
@ -191,6 +471,8 @@ fn children_to_tokens(
|
|||
parent_slots,
|
||||
global_class,
|
||||
view_marker,
|
||||
top_level,
|
||||
disable_inert_html,
|
||||
) {
|
||||
Some(tokens) => vec![tokens],
|
||||
None => vec![],
|
||||
|
@ -206,6 +488,8 @@ fn children_to_tokens(
|
|||
Some(&mut slots),
|
||||
global_class,
|
||||
view_marker,
|
||||
top_level,
|
||||
disable_inert_html,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
@ -227,7 +511,11 @@ fn node_to_tokens(
|
|||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||
global_class: Option<&TokenTree>,
|
||||
view_marker: Option<&str>,
|
||||
top_level: bool,
|
||||
disable_inert_html: bool,
|
||||
) -> Option<TokenStream> {
|
||||
let is_inert = !disable_inert_html && is_inert_element(node);
|
||||
|
||||
match node {
|
||||
Node::Comment(_) => None,
|
||||
Node::Doctype(node) => {
|
||||
|
@ -240,6 +528,7 @@ fn node_to_tokens(
|
|||
parent_slots,
|
||||
global_class,
|
||||
view_marker,
|
||||
disable_inert_html,
|
||||
),
|
||||
Node::Block(block) => Some(quote! { #block }),
|
||||
Node::Text(text) => Some(text_to_tokens(&text.value)),
|
||||
|
@ -248,13 +537,20 @@ fn node_to_tokens(
|
|||
let text = syn::LitStr::new(&text, raw.span());
|
||||
Some(text_to_tokens(&text))
|
||||
}
|
||||
Node::Element(node) => element_to_tokens(
|
||||
node,
|
||||
parent_type,
|
||||
parent_slots,
|
||||
global_class,
|
||||
view_marker,
|
||||
),
|
||||
Node::Element(el_node) => {
|
||||
if !top_level && is_inert {
|
||||
inert_element_to_tokens(node, global_class)
|
||||
} else {
|
||||
element_to_tokens(
|
||||
el_node,
|
||||
parent_type,
|
||||
parent_slots,
|
||||
global_class,
|
||||
view_marker,
|
||||
disable_inert_html,
|
||||
)
|
||||
}
|
||||
}
|
||||
Node::Custom(node) => Some(node.to_token_stream()),
|
||||
}
|
||||
}
|
||||
|
@ -278,6 +574,7 @@ pub(crate) fn element_to_tokens(
|
|||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||
global_class: Option<&TokenTree>,
|
||||
view_marker: Option<&str>,
|
||||
disable_inert_html: bool,
|
||||
) -> Option<TokenStream> {
|
||||
// attribute sorting:
|
||||
//
|
||||
|
@ -347,10 +644,16 @@ pub(crate) fn element_to_tokens(
|
|||
if is_component_node(node) {
|
||||
if let Some(slot) = get_slot(node) {
|
||||
let slot = slot.clone();
|
||||
slot_to_tokens(node, &slot, parent_slots, global_class);
|
||||
slot_to_tokens(
|
||||
node,
|
||||
&slot,
|
||||
parent_slots,
|
||||
global_class,
|
||||
disable_inert_html,
|
||||
);
|
||||
None
|
||||
} else {
|
||||
Some(component_to_tokens(node, global_class))
|
||||
Some(component_to_tokens(node, global_class, disable_inert_html))
|
||||
}
|
||||
} else if is_spread_marker(node) {
|
||||
let mut attributes = Vec::new();
|
||||
|
@ -467,6 +770,7 @@ pub(crate) fn element_to_tokens(
|
|||
parent_slots,
|
||||
global_class,
|
||||
view_marker,
|
||||
disable_inert_html,
|
||||
)
|
||||
} else {
|
||||
if !node.children.is_empty() {
|
||||
|
|
|
@ -11,6 +11,7 @@ pub(crate) fn slot_to_tokens(
|
|||
slot: &KeyedAttribute,
|
||||
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
|
||||
global_class: Option<&TokenTree>,
|
||||
disable_inert_html: bool,
|
||||
) {
|
||||
let name = slot.key.to_string();
|
||||
let name = name.trim();
|
||||
|
@ -118,6 +119,7 @@ pub(crate) fn slot_to_tokens(
|
|||
Some(&mut slots),
|
||||
global_class,
|
||||
None,
|
||||
disable_inert_html,
|
||||
);
|
||||
|
||||
// TODO view markers for hot-reloading
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use self::attribute::Attribute;
|
||||
use crate::{
|
||||
hydration::Cursor,
|
||||
no_attrs,
|
||||
renderer::Renderer,
|
||||
view::{Position, Render, RenderHtml},
|
||||
prelude::AddAnyAttr,
|
||||
renderer::{CastFrom, DomRenderer, Renderer},
|
||||
view::{Position, PositionState, Render, RenderHtml},
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
/// Types for HTML attributes.
|
||||
pub mod attribute;
|
||||
|
@ -76,8 +79,99 @@ where
|
|||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
_cursor: &crate::hydration::Cursor<R>,
|
||||
_position: &crate::view::PositionState,
|
||||
_cursor: &Cursor<R>,
|
||||
_position: &PositionState,
|
||||
) -> Self::State {
|
||||
}
|
||||
}
|
||||
|
||||
/// An element that contains no interactivity, and whose contents can be known at compile time.
|
||||
pub struct InertElement {
|
||||
html: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl InertElement {
|
||||
/// Creates a new inert element.
|
||||
pub fn new(html: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self { html: html.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr> Render<Rndr> for InertElement
|
||||
where
|
||||
Rndr: DomRenderer,
|
||||
{
|
||||
type State = Rndr::Element;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
Rndr::create_element_from_html(&self.html)
|
||||
}
|
||||
|
||||
fn rebuild(self, _state: &mut Self::State) {}
|
||||
}
|
||||
|
||||
impl<Rndr> AddAnyAttr<Rndr> for InertElement
|
||||
where
|
||||
Rndr: DomRenderer,
|
||||
{
|
||||
type Output<SomeNewAttr: Attribute<Rndr>> = Self;
|
||||
|
||||
fn add_any_attr<NewAttr: Attribute<Rndr>>(
|
||||
self,
|
||||
_attr: NewAttr,
|
||||
) -> Self::Output<NewAttr>
|
||||
where
|
||||
Self::Output<NewAttr>: RenderHtml<Rndr>,
|
||||
{
|
||||
panic!(
|
||||
"InertElement does not support adding attributes. It should only \
|
||||
be used as a child, and not returned at the top level."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr> RenderHtml<Rndr> for InertElement
|
||||
where
|
||||
Rndr: DomRenderer,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
|
||||
fn html_len(&self) -> usize {
|
||||
self.html.len()
|
||||
}
|
||||
|
||||
fn dry_resolve(&mut self) {}
|
||||
|
||||
async fn resolve(self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut Position,
|
||||
_escape: bool,
|
||||
_mark_branches: bool,
|
||||
) {
|
||||
buf.push_str(&self.html);
|
||||
*position = Position::NextChild;
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
let curr_position = position.get();
|
||||
if curr_position == Position::FirstChild {
|
||||
cursor.child();
|
||||
} else if curr_position != Position::Current {
|
||||
cursor.sibling();
|
||||
}
|
||||
let el = Rndr::Element::cast_from(cursor.current()).unwrap();
|
||||
position.set(Position::NextChild);
|
||||
el
|
||||
}
|
||||
}
|
||||
|
|
|
@ -390,6 +390,13 @@ impl DomRenderer for Dom {
|
|||
.unwrap()
|
||||
.unchecked_into()
|
||||
}
|
||||
|
||||
fn create_element_from_html(html: &str) -> Self::Element {
|
||||
// TODO can be optimized to cache HTML strings or cache <template>?
|
||||
let tpl = document().create_element("template").unwrap();
|
||||
tpl.set_inner_html(html);
|
||||
Self::clone_template(tpl.unchecked_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable<Dom> for Node {
|
||||
|
|
|
@ -300,6 +300,10 @@ impl DomRenderer for MockDom {
|
|||
fn clone_template(tpl: &Self::TemplateElement) -> Self::Element {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn create_element_from_html(html: &str) -> Self::Element {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Document {
|
||||
|
|
|
@ -213,8 +213,12 @@ pub trait DomRenderer: Renderer {
|
|||
fn get_template<V>() -> Self::TemplateElement
|
||||
where
|
||||
V: ToTemplate + 'static;
|
||||
|
||||
/// Deeply clones a template.
|
||||
fn clone_template(tpl: &Self::TemplateElement) -> Self::Element;
|
||||
|
||||
/// Creates a single element from a string of HTML.
|
||||
fn create_element_from_html(html: &str) -> Self::Element;
|
||||
}
|
||||
|
||||
/// Attempts to cast from one type to another.
|
||||
|
|
|
@ -560,6 +560,10 @@ impl DomRenderer for Sledgehammer {
|
|||
});
|
||||
node
|
||||
}
|
||||
|
||||
fn create_element_from_html(html: &str) -> Self::Element {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mountable<Sledgehammer> for SNode {
|
||||
|
|
Loading…
Reference in a new issue