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

Comment

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>)>>

Registers for Comment reference updates.

See DomRef for more information.

HtmlElement

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.

MathMlElement

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

Fields of MathMlElement

element: &'a Element<'a, S>

The Element to render.

dom_binding: Option<CallbackRef<S, fn(dom_ref: DomRef<&Element>)>>

Registers for Element reference updates.

See DomRef for more information.

SvgElement

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.

Memoized

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.

Multi(&'a [Node<'a, S>])

DOM-transparent. Represents a sequence of VDOM nodes.

Used to hint diffs in case of additions and removals.

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_keys 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.

Text

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>)>>

Registers for Text reference updates.

See DomRef for more information.

RemnantSite(&'a RemnantSite)

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]

#[must_use]
pub fn deanonymize(self) -> Self
[src]

👎 Deprecated:

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]

#[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]

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]

#[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]

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]

#[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]

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]

Not derived from the Into constraints on $Name directly since those are too broad.

fn align(self) -> T[src]

Contextually thread-binds an instance, or not. Use only without qualification.

fn align_ref(&self) -> &T[src]

Contextually thread-binds a reference, or not. Use only without qualification.

impl<'a, S> Clone for Node<'a, S> where
    S: ThreadSafety
[src]

fn clone(&self) -> Self[src]

Returns a copy of the value. Read more

fn clone_from(&mut self, source: &Self)1.0.0[src]

Performs copy-assignment from source. Read more

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]

Deanonymize towards a compatible concrete type. Read more

impl<'a, S> Debug for Node<'a, S> where
    S: ThreadSafety
[src]

fn fmt(&self, f: &mut Formatter<'_>) -> Result[src]

Formats the value using the given formatter. Read more

impl<'a, S1, S2> From<&'a [Node<'a, S1>]> for Node<'a, S2> where
    S1: ThreadSafety + Into<S2>,
    S2: ThreadSafety
[src]

fn from(content: &'a [Node<'a, S1>]) -> Self[src]

Performs the conversion.

impl<'a, S1, S2> From<&'a mut [Node<'a, S1>]> for Node<'a, S2> where
    S1: ThreadSafety + Into<S2>,
    S2: ThreadSafety
[src]

fn from(content: &'a mut [Node<'a, S1>]) -> Self[src]

Performs the conversion.

impl<'a, S> From<&'a mut str> for Node<'a, S> where
    S: ThreadSafety
[src]

fn from(text: &'a mut str) -> Self[src]

Performs the conversion.

impl<'a, S> From<&'a str> for Node<'a, S> where
    S: ThreadSafety
[src]

fn from(text: &'a str) -> Self[src]

Performs the conversion.

impl<'a> From<Node<'a, ThreadSafe>> for Node<'a, ThreadBound>[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]

fn hash<H: Hasher>(&self, state: &mut H)[src]

Feeds this value into the given Hasher. Read more

fn hash_slice<H>(data: &[Self], state: &mut H) where
    H: Hasher
1.3.0[src]

Feeds a slice of this type into the given Hasher. Read more

impl<'a, S> Ord for Node<'a, S> where
    S: ThreadSafety
[src]

fn cmp(&self, other: &Self) -> Ordering[src]

This method returns an Ordering between self and other. Read more

#[must_use]
fn max(self, other: Self) -> Self
1.21.0[src]

Compares and returns the maximum of two values. Read more

#[must_use]
fn min(self, other: Self) -> Self
1.21.0[src]

Compares and returns the minimum of two values. Read more

#[must_use]
fn clamp(self, min: Self, max: Self) -> Self
1.50.0[src]

Restrict a value to a certain interval. Read more

impl<'a, S1, S2> PartialEq<Node<'a, S2>> for Node<'a, S1> where
    S1: ThreadSafety,
    S2: ThreadSafety
[src]

fn eq(&self, other: &Node<'a, S2>) -> bool[src]

This method tests for self and other values to be equal, and is used by ==. Read more

#[must_use]
fn ne(&self, other: &Rhs) -> bool
1.0.0[src]

This method tests for !=.

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]

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]

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]

This method tests less than or equal to (for self and other) and is used by the <= operator. Read more

#[must_use]
fn gt(&self, other: &Rhs) -> bool
1.0.0[src]

This method tests greater than (for self and other) and is used by the > operator. Read more

#[must_use]
fn ge(&self, other: &Rhs) -> bool
1.0.0[src]

This method tests greater 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]

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]

impl<'a, S> Eq for Node<'a, S> where
    S: ThreadSafety
[src]

Auto Trait Implementations

impl<'a, S> RefUnwindSafe for Node<'a, S> where
    S: RefUnwindSafe

impl<'a, S> Send for Node<'a, S> where
    S: Send + Sync

impl<'a, S> Sync for Node<'a, S> where
    S: Sync

impl<'a, S> Unpin for Node<'a, S> where
    S: Unpin

impl<'a, S> UnwindSafe for Node<'a, S> where
    S: RefUnwindSafe + UnwindSafe

Blanket Implementations

impl<T> Any for T where
    T: 'static + ?Sized
[src]

pub fn type_id(&self) -> TypeId[src]

Gets the TypeId of self. Read more

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]

Deanonymize towards a compatible concrete type. Read more

impl<T> Borrow<T> for T where
    T: ?Sized
[src]

pub fn borrow(&self) -> &T[src]

Immutably borrows from an owned value. Read more

impl<T> BorrowMut<T> for T where
    T: ?Sized
[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]

#[must_use]
fn deanonymize(self) -> SafeVariant
[src]

Deanonymize towards a compatible concrete type. Read more

impl<T> From<T> for T[src]

pub fn from(t: T) -> T[src]

Performs the conversion.

impl<T, U> Into<U> for T where
    U: From<T>, 
[src]

pub fn into(self) -> U[src]

Performs the conversion.

impl<T> ToOwned for T where
    T: Clone
[src]

type Owned = T

The resulting type after obtaining ownership.

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]

🔬 This is a nightly-only experimental API. (toowned_clone_into)

recently added

Uses borrowed data to replace owned data, usually by cloning. Read more

impl<T, U> TryFrom<U> for T where
    U: Into<T>, 
[src]

type Error = Infallible

The type returned in the event of a conversion error.

pub fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>[src]

Performs the conversion.

impl<T, U> TryInto<U> for T where
    U: TryFrom<T>, 
[src]

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.

pub fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>[src]

Performs the conversion.