Enum lignin::Node [−][src]
pub enum Node<'a, S: ThreadSafety> { Comment { comment: &'a str, dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&Comment>)>>, }, HtmlElement { element: &'a Element<'a, S>, dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&HtmlElement>)>>, }, MathMlElement { element: &'a Element<'a, S>, dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&Element>)>>, }, SvgElement { element: &'a Element<'a, S>, dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&SvgElement>)>>, }, Memoized { state_key: u64, content: &'a Node<'a, S>, }, Multi(&'a [Node<'a, S>]), Keyed(&'a [ReorderableFragment<'a, S>]), Text { text: &'a str, dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&Text>)>>, }, RemnantSite(&'a RemnantSite), }
Expand description
Vdom
A single generic VDOM node.
This should be relatively small:
if size_of::<usize>() == 8 { assert!(size_of::<Node<ThreadSafe>>() <= 24); } // e.g. current Wasm if size_of::<usize>() == 4 { assert!(size_of::<Node<ThreadSafe>>() <= 16); }
Variants
Represents a Comment node.
Show fields
Fields of Comment
comment: &'a str
The comment’s body, as unescaped plaintext.
Renderers shouldn’t insert padding whitespace around it, except as required by e.g. pretty-printing.
Implementation Contract
This is not a soundness contract. Code using this crate must not rely on it for soundness. However, it is free to panic when encountering an incorrect implementation.
Security
This field may contain arbitrary character sequences, some of which are illegal in Comments at least when serialized as HTML. See https://html.spec.whatwg.org/multipage/syntax.html#comments for more information.
Renderers must either refuse or replace illegal-for-target comments with ones that are inert.
Not doing so opens the door for XSS and/or format confusion vulnerabilities.
dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&Comment>)>>
Represents a single HTMLElement.
Show fields
Fields of HtmlElement
element: &'a Element<'a, S>
The Element
to render.
dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&HtmlElement>)>>
Registers for HTMLElement reference updates.
See DomRef
for more information.
Represents a single MathMLElement.
Note that distinct browser support for these is really quite bad and correct styling isn’t much more available.
However, MathML is part of the HTML standard, so browsers should at least parse it correctly, and styling can be polyfilled.
Show fields
Represents a single SVGElement.
Note that even outermost <SVG>
elements are SVGElements!
Show fields
Fields of SvgElement
element: &'a Element<'a, S>
The Element
to render.
dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&SvgElement>)>>
Registers for SVGElement reference updates.
See DomRef
for more information.
DOM-transparent. This variant uses shallow comparison and hashes based on its state_key
only.
A (good enough) content
hash makes for a good state_key
, but this isn’t the only possible scheme and may not be the optimal one for your use case.
Implementation Contract (reminder)
Even if state_key
is unchanged between two VDOM iterations, the full contents must still be present in the second.
When skipping the memoized
content
, a renderer may still require this information to, for example, advance its DOM cursor.
Note that when diffing a non-Memoized
Node
into a Node::Memoized
(and vice-versa), renderers must still behave as if the DOM tree was recreated, which means cycling all Node reference bindings even if they match.
However, this often happens with matching or near-matching fragments during hydration of a web app.
If you already have a function to strip subscriptions (e.g. Node reference bindings) from a DOM and VDOM tree, or even just one to strip all callbacks (but this is less efficient), it’s likely more efficient to do so and then recurse.
Make sure the trees are actually somewhat compatible first, or you may end up processing the old VDOM twice for nothing.
Show fields
Fields of Memoized
state_key: u64
A value that’s (very likely to be) distinct between VDOM graphs where the path of two Node::Memoized
instances matches but their Node::Memoized::content
is distinct.
Consider using a (good enough) hash of content
for this purpose.
content: &'a Node<'a, S>
The VDOM tree memoized by this Node
.
DOM-transparent. Represents a sequence of VDOM nodes.
Used to hint diffs in case of additions and removals.
Keyed(&'a [ReorderableFragment<'a, S>])
A sequence of VDOM nodes that’s transparent at rest, but encodes information on how to reuse and reorder elements when diffing.
List indices are bad ReorderableFragment::dom_key
values unless reordered along with the items!
Use the Multi
variant instead if you don’t track component identity.
Implementation Contract
This is not a soundness contract. Code using this crate must not rely on it for soundness. However, it is free to panic when encountering an incorrect implementation.
The ReorderableFragment::dom_key
values must be unique within a slice referenced by a Node::Keyed
instance.
If a dom_key
value appears both in the initial and target slice of a ReorderableFragment::dom_key
diff,
those ReorderableFragment
instances are considered path-matching and any respective Node(s!) must
be moved to their new location without being recreated.
These rules do not apply between distinct
ReorderableFragment
slices, even if they overlap in memory or one is reachable from the other.
The recursive diff otherwise proceeds as normal. There are no rules on whether it happens before, during or after the reordering.
Usage Notes
ReorderableFragment::dom_key
is of type u32
and intentionally doesn’t fit Hasher
output.
It may be tempting to use a hash as easily-made DOM key, but this would violate the uniqueness constraint in the implementation contract above.
To derive unique dom_key
s from your application’s native IDs, you may want to use a symbol interner like intaglio.
When doing so, consider using a custom (Build
)Hasher
to reduce the code size of your compiled program.
A “bad” hash won’t hurt your app if individual symbol tables are small.
Example
use bumpalo::Bump; // CRC32 is about as simple a hash as is compatible with `core::hash::Hash`. use crc32fast::Hasher; use intaglio::SymbolTable; use lignin::{Node, ReorderableFragment, ThreadSafe}; use std::{borrow::Cow, hash::BuildHasherDefault}; // `.unwrap_throw()` produces smaller executables when targeting Wasm. // However, the error message may be less specific. use wasm_bindgen::UnwrapThrowExt as _; // Persist this in your component. let mut key_map: SymbolTable<BuildHasherDefault<Hasher>> = SymbolTable::default(); // Fake data: let items = ["Prepare for trouble!", "And make it double!"]; // Fake render parameter: let bump = Bump::new(); // Render: let _ = Node::Keyed::<ThreadSafe>(bump.alloc_slice_fill_iter(items.iter().map(|item| { ReorderableFragment { dom_key: key_map.intern(Cow::Borrowed(*item)).unwrap_throw().id(), content: Node::Text { text: item, dom_binding: None, }, } })));
Motivation
You may have noticed that the implementation contract for
Node::Keyed
is unusually strict compared to other frameworks.There are two reasons for this:
Ease of Implementation
Without having to account for duplicate keys, it’s easier to implement an efficient DOM differ.
Accessibility and Glitch Avoidance
More importantly, duplicate DOM keys can introduce subtle UX glitches into your app, which can shift focus in unexpected ways inside a list. (This can most easily occur when dismissing a duplicate item with contained button.)
These glitches are likely imperceptible to the majority of your users, unless they navigate by keyboard, or use a screen reader, or your focus style is quite visible, or items animate in or out or to their new position, … the list of edge cases is really quite long. The point is that these issues may be subtle during early development but can surface in force later, when it’s difficult to fix them thoroughly.
lignin
avoids this by denying the necessary circumstance (duplication of keys) for them to occur in the first place.
Represents a Text node.
Show fields
Fields of Text
text: &'a str
The Text
’s Node.textContent.
Implementation Contract
This is not a soundness contract. Code using this crate must not rely on it for soundness. However, it is free to panic when encountering an incorrect implementation.
Security
This field contains unescaped plaintext. Renderers must escape all control characters and sequences.
Not doing so opens the door for XSS vulnerabilities.
In order to support e.g. formatting instructions, apps should (carefully) parse user-generated content and translate it into a matching VDOM graph.
Live components also have the option of using for example Node::HtmlElement::dom_binding
to set Element.innerHTML,
but this is not recommended due to the difficulty of implementing allow-listing with such an approach.
dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&Text>)>>
Currently unused.
The plan here is to allow fragments to linger in the DOM after being diffed out, which seems like the most economical way to enable e.g. fade-out animations.
Implementations
impl<'a, S: ThreadSafety> Node<'a, S>
[src]
impl<'a, S: ThreadSafety> Node<'a, S>
[src]#[must_use]pub fn deanonymize(self) -> Self
[src]
👎 Deprecated: Call of .deanonymize()
on named type.
#[must_use]pub fn deanonymize(self) -> Self
[src]Call of .deanonymize()
on named type.
When called on an opaque type, deanonymizes it into the underlying named type.
Both AutoSafe
and Deanonymize
must be in scope and the method must be called without qualification for this to work.
Calling this method on a named type returns the value and type unchanged and produces a deprecation warning.
impl<'a> Node<'a, ThreadSafe>
[src]
impl<'a> Node<'a, ThreadSafe>
[src]#[must_use]pub fn prefer_thread_safe(self) -> Self
[src]
#[must_use]pub fn prefer_thread_safe(self) -> Self
[src]Gently nudges the compiler to choose the ThreadSafe
version of a value if both are possible.
This method is by value, so it will resolve with higher priority than the by-reference method on the ThreadBound
type.
Note that not all tooling will show the correct overload here, but the compiler knows which to pick.
#[must_use]pub fn prefer_thread_safe_ref(&self) -> &Self
[src]
#[must_use]pub fn prefer_thread_safe_ref(&self) -> &Self
[src]Gently nudges the compiler to choose the ThreadSafe
version of a reference if both are possible.
This method is once by single reference, so it will resolve with higher priority than the twice-by-reference method on the ThreadBound
type.
Note that not all tooling will show the correct overload here, but the compiler knows which to pick.
impl<'a> Node<'a, ThreadBound>
[src]
impl<'a> Node<'a, ThreadBound>
[src]#[must_use]pub fn prefer_thread_safe(&self) -> Self
[src]
#[must_use]pub fn prefer_thread_safe(&self) -> Self
[src]Gently nudges the compiler to choose the ThreadSafe
version of a value if both are is possible.
This method is by reference, so it will resolve with lower priority than the by-value method on the ThreadSafe
type.
Note that not all tooling will show the correct overload here, but the compiler knows which to pick.
#[must_use]pub fn prefer_thread_safe_ref<'b>(self: &&'b Self) -> &'b Self
[src]
#[must_use]pub fn prefer_thread_safe_ref<'b>(self: &&'b Self) -> &'b Self
[src]Gently nudges the compiler to choose the ThreadSafe
version of a reference if both are is possible.
This method is twice by reference, so it will resolve with lower priority than the once-by-reference method on the ThreadSafe
type.
Note that not all tooling will show the correct overload here, but the compiler knows which to pick.
impl<'a, S: ThreadSafety> Node<'a, S>
[src]
impl<'a, S: ThreadSafety> Node<'a, S>
[src]#[must_use]pub fn dom_len(&self) -> usize
[src]
#[must_use]pub fn dom_len(&self) -> usize
[src]Calculates the aggregate surface level length of this Node
in Nodes.
This operation is recursive across for example Node::Multi
and Node::Keyed
, which sum up their contents in this regard.
#[must_use]pub fn dom_empty(&self) -> bool
[src]
#[must_use]pub fn dom_empty(&self) -> bool
[src]Determines whether this Node
represents no Nodes at all.
This operation is recursive across for example Node::Multi
and Node::Keyed
, which sum up their contents in this regard.
Trait Implementations
impl<'a, S1, S2> Align<Node<'a, S2>> for Node<'a, S1> where
S1: ThreadSafety + Into<S2>,
S2: ThreadSafety,
[src]
impl<'a, S1, S2> Align<Node<'a, S2>> for Node<'a, S1> where
S1: ThreadSafety + Into<S2>,
S2: ThreadSafety,
[src]Not derived from the Into
constraints on $Name
directly since those are too broad.
impl<'a, S> Clone for Node<'a, S> where
S: ThreadSafety,
[src]
impl<'a, S> Clone for Node<'a, S> where
S: ThreadSafety,
[src]impl<'a, V> Deanonymize<Node<'a, ThreadSafe>> for V where
V: Send + Sync + AutoSafe<Node<'a, ThreadBound>>,
[src]
impl<'a, V> Deanonymize<Node<'a, ThreadSafe>> for V where
V: Send + Sync + AutoSafe<Node<'a, ThreadBound>>,
[src]#[must_use]fn deanonymize(self) -> SafeVariant
[src]
#[must_use]fn deanonymize(self) -> SafeVariant
[src]Deanonymize towards a compatible concrete type. Read more
impl<'a, S> Debug for Node<'a, S> where
S: ThreadSafety,
[src]
impl<'a, S> Debug for Node<'a, S> where
S: ThreadSafety,
[src]impl<'a, S1, S2> From<&'a [Node<'a, S1>]> for Node<'a, S2> where
S1: ThreadSafety + Into<S2>,
S2: ThreadSafety,
[src]
impl<'a, S1, S2> From<&'a [Node<'a, S1>]> for Node<'a, S2> where
S1: ThreadSafety + Into<S2>,
S2: ThreadSafety,
[src]impl<'a, S1, S2> From<&'a mut [Node<'a, S1>]> for Node<'a, S2> where
S1: ThreadSafety + Into<S2>,
S2: ThreadSafety,
[src]
impl<'a, S1, S2> From<&'a mut [Node<'a, S1>]> for Node<'a, S2> where
S1: ThreadSafety + Into<S2>,
S2: ThreadSafety,
[src]impl<'a, S> From<&'a mut str> for Node<'a, S> where
S: ThreadSafety,
[src]
impl<'a, S> From<&'a mut str> for Node<'a, S> where
S: ThreadSafety,
[src]impl<'a, S> From<&'a str> for Node<'a, S> where
S: ThreadSafety,
[src]
impl<'a, S> From<&'a str> for Node<'a, S> where
S: ThreadSafety,
[src]impl<'a> From<Node<'a, ThreadSafe>> for Node<'a, ThreadBound>
[src]
impl<'a> From<Node<'a, ThreadSafe>> for Node<'a, ThreadBound>
[src]fn from(thread_safe: Node<'a, ThreadSafe>) -> Self
[src]
fn from(thread_safe: Node<'a, ThreadSafe>) -> Self
[src]Performs the conversion.
impl<'a, S> Hash for Node<'a, S> where
S: ThreadSafety,
[src]
impl<'a, S> Hash for Node<'a, S> where
S: ThreadSafety,
[src]impl<'a, S> Ord for Node<'a, S> where
S: ThreadSafety,
[src]
impl<'a, S> Ord for Node<'a, S> where
S: ThreadSafety,
[src]impl<'a, S1, S2> PartialEq<Node<'a, S2>> for Node<'a, S1> where
S1: ThreadSafety,
S2: ThreadSafety,
[src]
impl<'a, S1, S2> PartialEq<Node<'a, S2>> for Node<'a, S1> where
S1: ThreadSafety,
S2: ThreadSafety,
[src]impl<'a, S1, S2> PartialOrd<Node<'a, S2>> for Node<'a, S1> where
S1: ThreadSafety,
S2: ThreadSafety,
[src]
impl<'a, S1, S2> PartialOrd<Node<'a, S2>> for Node<'a, S1> where
S1: ThreadSafety,
S2: ThreadSafety,
[src]fn partial_cmp(&self, other: &Node<'a, S2>) -> Option<Ordering>
[src]
fn partial_cmp(&self, other: &Node<'a, S2>) -> Option<Ordering>
[src]This method returns an ordering between self
and other
values if one exists. Read more
#[must_use]fn lt(&self, other: &Rhs) -> bool
1.0.0[src]
#[must_use]fn lt(&self, other: &Rhs) -> bool
1.0.0[src]This method tests less than (for self
and other
) and is used by the <
operator. Read more
#[must_use]fn le(&self, other: &Rhs) -> bool
1.0.0[src]
#[must_use]fn le(&self, other: &Rhs) -> bool
1.0.0[src]This method tests less than or equal to (for self
and other
) and is used by the <=
operator. Read more
impl<'a, S> Vdom for Node<'a, S> where
S: ThreadSafety,
[src]
impl<'a, S> Vdom for Node<'a, S> where
S: ThreadSafety,
[src]type ThreadSafety = S
type ThreadSafety = S
The ThreadSafety
of the Vdom
type, either ThreadSafe
or ThreadBound
. Read more
impl<'a, S> Copy for Node<'a, S> where
S: ThreadSafety,
[src]
S: ThreadSafety,
impl<'a, S> Eq for Node<'a, S> where
S: ThreadSafety,
[src]
S: ThreadSafety,
Auto Trait Implementations
impl<'a, S> RefUnwindSafe for Node<'a, S> where
S: RefUnwindSafe,
S: RefUnwindSafe,
impl<'a, S> Send for Node<'a, S> where
S: Send + Sync,
S: Send + Sync,
impl<'a, S> Sync for Node<'a, S> where
S: Sync,
S: Sync,
impl<'a, S> Unpin for Node<'a, S> where
S: Unpin,
S: Unpin,
impl<'a, S> UnwindSafe for Node<'a, S> where
S: RefUnwindSafe + UnwindSafe,
S: RefUnwindSafe + UnwindSafe,
Blanket Implementations
impl<'a, S, T> AutoSafe<T> for S where
S: Vdom + Align<T>,
T: Vdom<ThreadSafety = ThreadBound>,
[src]
impl<'a, S, T> AutoSafe<T> for S where
S: Vdom + Align<T>,
T: Vdom<ThreadSafety = ThreadBound>,
[src]#[must_use]fn deanonymize(&self) -> BoundVariant
[src]
#[must_use]fn deanonymize(&self) -> BoundVariant
[src]Deanonymize towards a compatible concrete type. Read more
impl<T> BorrowMut<T> for T where
T: ?Sized,
[src]
impl<T> BorrowMut<T> for T where
T: ?Sized,
[src]pub fn borrow_mut(&mut self) -> &mut T
[src]
pub fn borrow_mut(&mut self) -> &mut T
[src]Mutably borrows from an owned value. Read more
impl<'a, V> Deanonymize<Node<'a, ThreadSafe>> for V where
V: Send + Sync + AutoSafe<Node<'a, ThreadBound>>,
[src]
impl<'a, V> Deanonymize<Node<'a, ThreadSafe>> for V where
V: Send + Sync + AutoSafe<Node<'a, ThreadBound>>,
[src]#[must_use]fn deanonymize(self) -> SafeVariant
[src]
#[must_use]fn deanonymize(self) -> SafeVariant
[src]Deanonymize towards a compatible concrete type. Read more
impl<T> ToOwned for T where
T: Clone,
[src]
impl<T> ToOwned for T where
T: Clone,
[src]type Owned = T
type Owned = T
The resulting type after obtaining ownership.
pub fn to_owned(&self) -> T
[src]
pub fn to_owned(&self) -> T
[src]Creates owned data from borrowed data, usually by cloning. Read more
pub fn clone_into(&self, target: &mut T)
[src]
pub fn clone_into(&self, target: &mut T)
[src]🔬 This is a nightly-only experimental API. (toowned_clone_into
)
recently added
Uses borrowed data to replace owned data, usually by cloning. Read more