neoview 0.0.4

a reactive, lightweight and safe declarative UI framework
Documentation
//! # NeoView
//! NeoView is a lightweight, modern declarative UI framework that prioritizes robustness, safety, and efficiency over complex runtime magic.
//!
//! Aligned with Rust's core principles, NeoView offers a practical middle ground in declarative UI design. It supports ergonomic, fully reactive UI definitions with a strong emphasis on safety, robustness, efficiency, and renderer agnosticism.
//!
//! This crate provides the core components used by all renderers. For more information about a specific renderer, see its respective crate.
//!
//! # Reactive System
//! Inspired by SolidJS and Leptos, NeoView is powered by fine-grained reactivity. However, instead of relying on anonymous signals that carry heavy overhead, it utilizes a context-passing approach that satisfies the borrow checker while ensuring clear ownership and minimal overhead.
//!
//! Reactive states (also called properties) are stored in [`Store`]. These states are created by [`prop`](Store::prop) and accessed by ID ([`PropId`]) through methods like [`read`](Store::read), [`write`](Store::write), [`get`](Store::get), and [`update`](Store::update) on the [`Store`].
//! ```rust
//! let ctx = /* some `Context` */
//! let nb = ctx.prop(1);
//! assert_eq!(ctx.get(nb), 1);
//! ctx.write(nb, 2);
//! assert_eq!(ctx.get(nb), 2);
//! ```
//!
//! Reactive logic can be placed inside [`effect`s](Store::effect), and derived properties are created via [`computed`](Store::computed).
//! ```rust
//! let nb = ctx.prop(1);
//! ctx.effect(move |ctx| println!("nb: {}", ctx.get(nb)));
//! ctx.write(nb, 2); // => nb: 2
//! ```
//!
//! Any place that accesses state requires mutable access to a [`Context`], which is the type that owns the [`Store`], the UI, and everything related to it.
//!
//! [`Context`] and any type that provides access to the [`Store`] implement [`StoreProv`]ider, exposing the common methods of the [`Store`] directly.
//!
//! # Templating
//! NeoView utilizes a templating approach called chunked templating. The UI is constructed from multiple interleaved chunks, each chunk contains its own inlined logic and can host nested subchunks. This enables localized, nested UIs without requiring an excessive number of micro-components.
//!
//! The [`chunk`] macro allows writing UIs in a simple, expressive, object-like syntax.
//! ```rust
//! // using neoview-web
//! chunk!(build, div {
//!     h1 { "counter" }
//!     do {
//!         let count = build.prop(0);
//!         chunk!(build, button(
//!             on.click: (move |ctx, _| ctx.update(count, |v| *v += 1))
//!         ) { "count: ", count });
//!     }
//! });
//! ```
//!
//! The UI is constructed at init time in an imperative style, and updates flow directly to specific elements using fine-grained reactivity.
//!
//! All Rust control flow and even all imperative patterns can be used inside chunks. There is no custom syntax for components, they are simply functions that borrow the context.
//!
//! ```rust
//! fn counter(build: &mut ChunkBuild, name: PropId<String>) {
//!     let count = build.prop(0);
//!     chunk!(build, button(
//!         on.click: (move |ctx, _| ctx.update(count, |v| *v += 1))
//!     ) { name, ": ", count });
//! }
//! fn main() {
//!     // ...
//!     chunk!(build, div {
//!         for name in 'a'..'z' {
//!             let name = build.prop(name.to_string());
//!             counter(build, name);
//!         }
//!     })
//! }
//! ```
//!
//! Note that the structure generated by these chunks is static rather than dynamic.
//!
//! # Renderers
//! NeoView is renderer-agnostic, it supports any platform or renderer, provided they implement the necessary items.
//!
//! It provides only the sharable parts between the renderers (the reactivity system and the chunk language) and also the philosophy, the unique rest is lift to the renderer.
//!
//! The available renderers include:
//! - [`neoview-web`](https://docs.rs/neoview_web/latest/neoview_web/): A renderer targeting the web platform based on HTML and the DOM.

mod context;
mod prop;
mod store;
mod updater;

/// Constructs a UI chunk in an expressive, object-like syntax.
///
/// `chunk` is a macro that defines and appends a UI chunk to the given chunk build using a universal, expressive syntax defined by the renderer.
///
/// `chunk` acts as a bridge. It parses a universal and raw element tree syntax into a series of [buildcodes](#buildcodes). These buildcodes are defined by the renderer, which further refines and restricts the syntax.
///
/// `chunk` allows the same expressive syntax to be universal across all renderers, however, each renderer has the right to interpret and refine the syntax according to its needs.
///
/// `chunk` is not the only way of templating, the renderer can provide additional templating methods.
///
/// # Example
/// ```
/// // using neoview-web
/// chunk!(build, div {
///     h1 { "counter" }
///     do {
///         let count = build.prop(0);
///         chunk!(build, button(
///             on.click: (move |ctx, _| ctx.update(count, |v| *v += 1))
///         ) { "count: ", count });
///     }
/// });
/// ```
///
/// ### Chunk Builds
/// Chunk builds are types that construct ui, they are passed to `chunk` macro and to the buildcodes, and are renderer defined.
///
/// They bahave like a tree builder not a linear one, chunks can be appended to them at any point inside the ui structure.
///
/// It is suggest to implement [`StoreProv`] or [`ScopedStoreProv`] for them to make them more ergonomic.
///
/// # Syntax
/// This section uses the [gramex meta language](https://docs.rs/gramex/latest/gramex/docs/gram_ref/index.html).
///
/// ### `chunk` Arguments
/// ```text
/// let chunk_args = (build = expr) "," children;
/// ```
/// The `chunk` macro takes two arguments: a `build` expression (whose type is defined by the renderer), and a list of children to be appended to the build at the target point.
///
/// ### Children
/// ```text
/// let children = (child_opt_comma | child_req_comma) (","? child_opt_comma | "," child_req_comma)* ","?;
/// let child_opt_comma = element | do_block;
/// let child_req_comma = content;
/// ```
/// The chunk syntax represents a tree of element children, where children are items that can be nested inside an element.
///
/// A comma is used to separate children. Some items have it as optional ([elements](#element) and [do blocks](#do-block)), while others require it ([contents](#content)). A trailing comma is allowed.
///
/// ```
/// chunk!(build,
///     el {} do {} "content", "other content", el {}
///     // same as:
///     el {}, do {}, "content", "other content", el {},
/// );
/// ```
///
/// ### Element
/// ```text
/// let element = (tag = path | str_lit) (attrs | body | attrs body);
/// let attrs = "(" list<ident | _+ ":" _+, ",">? ","? ")";
/// let body = "{" children? "}";
/// ```
/// An element is a UI element defined by a tag, and it can have attributes, children, or both.
///
/// A tag can be a [path](https://doc.rust-lang.org/reference/paths.html#simple-paths) or a string literal.
///
/// Attributes are a comma-separated list of `name: value` pairs enclosed inside parentheses (`()`), where the name and value can be any token list not containing a `,` or `:`.
///
/// A single identifier can be used as an attribute as a shorthand when both the name and the value are the same identifier.
///
/// The body is a curly brace (`{}`) block optionally containing children.
///
/// Tags, attribute names, and values are kept raw to support any renderer; the renderer will further restrict and refine them for its needs.
///
/// ```
/// chunk!(build,
///     el(attr1: value, attr2)
///     ns::el { "content", child {} }
///     "some-el"(ns.attr: (|a, b| a + b)) { "content" }
/// );
/// ```
///
/// ### Do Block
/// ```text
/// let do_block =
///     "do" "{" _* "}" | "for" _+ "{" _* "}" | "match" _+ "{" _* "}" |
///     "if" _+ "{" _* "}" ("else" _+ "{" _* "}")*
/// ;
/// ```
/// Do blocks are expression blocks that are evaluated when execution reaches where the block is defined inside the tree.
///
/// They are a unique feature to `neocomp` that allow inlining logic within the UI and separating the UI into multiple nested chunks.
///
/// Do blocks are defined using the `do` keyword followed by an expression block. There are also direct shorthands for `if`, `for`, and `match` expressions.
///
/// Note: Do blocks and their shorthands are static and not dynamic. For dynamic versions, see your renderer's documentation.
///
/// ```
/// chunk!(build, div {
///     "after this",
///     do {
///            let count = build.prop(0);
///         chunk!(build, button(
///             on.click: (move |ctx, _| ctx.update(count, |v| *v += 1))
///         ) { "count: ", count });
///     }
///     "before this",
///
///     for name in 'a'..'z' {
///            chunk!(build, div { name });
///     }
///     if a > b {
///         "greater"
///     } else {
///         "less"
///     }
///     match nb {
///         1 => "one",
///         2 => "two",
///         _ => "other",
///     }
/// });
/// ```
///
/// ### Content
/// ```text
/// let content = _+;
/// ```
/// Content is any token list that is not an element or a do block.
///
/// It can be a string literal, an expression, or any other token list defined by the renderer.
///
/// ```
/// chunk!(build, "content", variable, 1 + 1, [1, 2, 3], move |ctx| ctx.get(prop));
/// ```
///
/// # Buildcodes
/// This section is meant for renderer maintainers.
///
/// The `chunk` macro transforms the element tree into a series of calls to buildcodes.
///
/// Buildcodes are macros defined inside a module named `__buildcode` within the caller's scope.
///
/// ### Chunk Buildcodes
/// ```
/// macro_rules! start_chunk {
///     ($build:expr) => { el:expr }
/// }
/// macro_rules! end_chunk {
///     ($build:expr, $el:expr) => {}
/// }
/// ```
///
/// The `chunk` macro starts by storing the `build` argument into a local variable so that it can be passed to all buildcodes.
///
/// Then, it calls `start_chunk` to adjust the build and returns the parent element of the top point.
///
/// Next, it calls the buildcodes of the top-level children.
///
/// Finally, it calls `end_chunk` with the parent element of the top point to end the chunk.
///
/// ### Element Buildcodes
/// ```
/// macro_rules! start_el {
///     ($build:expr, $el:expr, $($tag:tt)+) => { $el:expr };
/// }
/// macro_rules! attr {
///     ($build:expr, $el:expr, [$($name:tt)+], $($value:tt)+) => { };
/// }
/// macro_rules! end_el {
///     ($build:expr, $parent:expr, $el:expr, $($tag:tt)+) => { $el:expr };
/// }
/// ```
/// An element is transformed into a call to `start_el` with the tag tokens and the parent element to return the new element.
///
/// Then, attributes are transformed into calls to `attr` with the name tokens, value tokens, and the element.
///
/// Next, calls are made to the children's buildcodes.
///
/// Finally, a call to `end_el` is made with the parent element, the new element, and the tag tokens.
///
/// The element's type is left up to the renderer, and it can be `()` if not needed.
///
/// ### Content Buildcode
/// ```
/// macro_rules! content {
///     ($build:expr, $el:expr, $($content:tt)+) => { };
/// }
/// ```
/// `content` is called for every piece of content with the element and the content tokens.
///
/// ### Do Block Buildcodes
/// ```
/// macro_rules! start_do_block {
///     ($build:expr, $el:expr) => { };
/// }
/// macro_rules! end_do_block {
///     ($build:expr, $el:expr) => { };
/// }
/// ```
/// `start_do_block` and `end_do_block` are called for every do block with the element.
///
/// A do block is transformed into an expression block containing a call to `start_do_block`, followed by the block contents, and finally a call to `end_do_block`.
///
/// ### Example
/// ```
/// chunk!(build, div {
///     span(id: "hello") { "world" }
///     do { println!("hello") }
/// });
///
///    // will be transformed into something like:
/// {
///     let mut build = build;
///     let mut el = __buildcode::start_chunk!(build);
///        let mut child = {
///         let mut el = __buildcode::start_el!(build, el, div);
///         let mut child = {
///                let el = __buildcode::start_el!(build, el, span);
///                __buildcode::attr!(build, el, [id], "hello");
///                __buildcode::content!(build, el, "world");
///             el
///            };
///            __buildcode::end_el!(build, el, child, span);
///         {
///             __buildcode::start_do_block!(build, el);
///             println!("hello");
///             __buildcode::end_do_block!(build, el);
///         }
///         el
///        };
///     __buildcode::end_el!(build, el, child, div);
///     __buildcode::end_chunk!(build, el);
/// }
/// ```
pub use neoview_macro::chunk;

pub use {
	context::{Context, GlobalStoreProv, ScopedStoreProv, StoreProv},
	prop::{PropId, SlabId},
	store::{EffectDeps, Store, TrackResult},
};

/// an error raised by the reactivity system.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
	/// the requested thing is removed.
	Removed,
	/// duplicated propids were given to [`Store::try_read_disjoint_mut`]
	NotDisjoint,
	/// requested to track while curently tracking.
	Tracking,
	/// requesting to end tracking while not tracking currently.
	NotTracking,
}