avalanche/lib.rs
1/// Provides useful hooks and supporting utilities.
2pub mod hooks;
3/// Holds platform-specific rendering interfaces.
4pub mod renderer;
5/// A reference-counted interior-mutable type designed to reduce runtime borrow rule violations.
6pub mod shared;
7/// Holds types and macros used for propogating and tracking data updates.
8pub mod tracked;
9/// A `Vec`-backed implementation of a tree, with a relatively friendly mutation api.
10pub(crate) mod tree;
11/// An in-memory representation of the current component tree.
12#[doc(hidden)]
13pub mod vdom;
14
15use downcast_rs::{impl_downcast, Downcast};
16use std::rc::Rc;
17
18use renderer::NativeType;
19use shared::Shared;
20use tree::NodeId;
21use vdom::{VDom, VNode};
22
23pub use hooks::{state, vec, Context};
24pub use tracked::Tracked;
25
26/// An attribute macro used to define components.
27///
28/// # Basic usage
29/// The macro must be applied to a function that returns a [View](View).
30/// It generates a `struct` implementing [Component](Component) with the name of the input function, and a builder struct.
31/// The function's name should start with a capital ASCII character.
32///
33/// The function can optionally take parameters. Parameter types must be `Clone` and `'static`.
34/// Parameters must have concrete types: they cannot use the `impl Trait`
35/// syntax. Components cannot be `async` or generic.
36///
37/// A component must return a [View] describing what it will render.
38/// Components are invoked with the same syntax as function calls, except with
39/// the macro `!` after the type name:
40/// ```rust
41/// use avalanche::{component, tracked, View};
42/// use avalanche_web::components::{H1, Text};
43///
44/// const class: &str = "hello-world";
45///
46/// #[component]
47/// pub fn HelloWorld(name: String) -> View {
48/// H1!(
49/// id: class,
50/// class: class,
51/// child: Text!(format!("Hi there, {}!", tracked!(name)))
52/// )
53/// }
54/// ```
55///
56/// In the case of `H1`, all of its parameters are optional and have default values, but for components generated by this
57/// macro, all parameters must be provided, or component instantiation will panic at runtime. In the future, this will
58/// instead be a compile-time error.
59///
60/// Note that all macro invocations beginning with a capital ASCII character will be interpreted as component invocations
61/// within the function. If you need to invoke a macro beginning with a capital letter, consider using it with an alias
62/// beginning with `_`.
63///
64/// ## Tracked values
65/// Parameter values are passed into a function with the [Tracked](Tracked) wrapper. More details on how that works is found in
66/// the [tracked](tracked!) macro documentation.
67///
68/// ## Avoid side effects and mutability
69/// `avalanche` considers a parameter updated if one of the parameters or hooks that influence it change, but
70/// a function like [rand::thread_rng](https://docs.rs/rand/0.8/rand/fn.thread_rng.html) has a different value on every call
71/// despite having no parameter or hook dependencies.
72/// Using values from functions and methods that are not pure or mutate values will lead to missed updates. Instead, create new variables:
73/// use `let string = string + " appended"` instead of `string += "appended"`.
74/// If you need to use mutation in a component, including interior mutability, use the [state](state) hook.
75///
76/// ## Eschew third-party macros
77/// Unfortunately, macros are syntax extensions and `component` cannot keep track of tracked variables in most of them.
78/// All `std` macros (like [vec!](std::vec!()) and [format!](std::format!())) and `avalanche` macros (like [enclose!()])
79/// work well, but any others may lead to parameters being incorrectly marked as not updated.
80#[doc(inline)]
81pub use avalanche_macro::component;
82
83/// Takes a list of identifiers terminated by a semicolon and expression. Each identifier is
84/// cloned, and made available to the expression. The macro evaluates to that expression.
85/// This is useful for passing things like state and setters to multiple component props.
86/// # Example
87/// ```rust
88/// # use avalanche::enclose;
89/// let message = "Enclose me!".to_owned();
90/// let closure1 = enclose!(message; move || println!("{}", message));
91/// let closure2 = enclose!(message; move || eprintln!("{}", message));
92/// ```
93#[macro_export]
94macro_rules! enclose {
95 ( $( $x:ident ),*; $y:expr ) => {
96 {
97 $(let $x = $x.clone();)*
98 $y
99 }
100 };
101}
102
103/// For internal use only, does not obey semver and is unsupported
104/// A hack used to work around a seeming syn limitation
105/// syn will interpret some macro calls as items rather than expressions
106/// syn expects an item to be replaced by another, so when parsing Component! type macros,
107/// `#[component]` will replace that call with an expression within this macro
108/// to syn, this is replacing one macro item with another
109#[doc(hidden)]
110#[macro_export]
111macro_rules! __internal_identity {
112 ($e:expr) => {
113 $e
114 };
115}
116
117/// A reference-counted type that holds an instance of a component.
118/// Component functions must return a [`View`].
119#[derive(Clone)]
120pub struct View {
121 rc: Rc<dyn DynComponent>,
122}
123
124impl View {
125 fn new<T: DynComponent>(val: T) -> Self {
126 Self { rc: Rc::new(val) }
127 }
128}
129
130impl std::ops::Deref for View {
131 type Target = dyn DynComponent;
132
133 fn deref(&self) -> &dyn DynComponent {
134 &*self.rc
135 }
136}
137
138impl<T: DynComponent> From<T> for View {
139 fn from(val: T) -> Self {
140 Self::new(val)
141 }
142}
143
144impl<T: DynComponent> From<Option<T>> for View {
145 fn from(val: Option<T>) -> Self {
146 match val {
147 Some(val) => View::new(val),
148 None => View::new(()),
149 }
150 }
151}
152
153impl From<Option<View>> for View {
154 fn from(val: Option<View>) -> Self {
155 match val {
156 Some(val) => val,
157 None => View::new(()),
158 }
159 }
160}
161
162/// The trait representing a component.
163///
164/// Users should not implement this trait manually but instead use the `component` attribute.
165/// However, native component implementations may need to use manual component implementations.
166pub trait Component: 'static {
167 type Builder;
168
169 fn render(&self, ctx: Context) -> View;
170
171 fn updated(&self) -> bool;
172
173 fn native_type(&self) -> Option<NativeType> {
174 None
175 }
176
177 fn location(&self) -> Option<(u32, u32)> {
178 None
179 }
180
181 fn key(&self) -> Option<&str> {
182 None
183 }
184}
185
186impl Component for () {
187 // TODO: make ! when never stabilizes
188 type Builder = ();
189
190 fn render(&self, _: Context) -> View {
191 unreachable!()
192 }
193 fn updated(&self) -> bool {
194 false
195 }
196}
197
198/// An internal trait implemented for all [`Component`]s. This should not be
199/// implemented manually.
200#[doc(hidden)]
201pub trait DynComponent: Downcast + 'static {
202 fn render(&self, ctx: Context) -> View;
203
204 fn native_type(&self) -> Option<NativeType>;
205
206 fn updated(&self) -> bool;
207
208 fn location(&self) -> Option<(u32, u32)>;
209
210 fn key(&self) -> Option<&str>;
211}
212
213impl_downcast!(DynComponent);
214
215impl<T: Component> DynComponent for T {
216 fn render(&self, ctx: Context) -> View {
217 Component::render(self, ctx)
218 }
219
220 fn native_type(&self) -> Option<NativeType> {
221 Component::native_type(self)
222 }
223
224 fn updated(&self) -> bool {
225 Component::updated(self)
226 }
227
228 fn location(&self) -> Option<(u32, u32)> {
229 Component::location(self)
230 }
231
232 fn key(&self) -> Option<&str> {
233 Component::key(self)
234 }
235}
236
237#[doc(hidden)]
238#[derive(Copy, Clone)]
239pub struct ComponentNodeId {
240 pub(crate) id: NodeId<VNode>,
241}
242
243impl From<NodeId<VNode>> for ComponentNodeId {
244 fn from(node: NodeId<VNode>) -> Self {
245 Self { id: node }
246 }
247}
248
249#[doc(hidden)]
250#[derive(Copy, Clone)]
251/// Internal data structure that stores what tree a component
252/// belongs to, and its position within it
253pub struct ComponentPos<'a> {
254 /// Shared value ONLY for passing to UseState
255 /// within the render function this value is mutably borrowed,
256 /// so exec and exec_mut will panic
257 pub node_id: ComponentNodeId,
258 /// Shared container to the VDom of which the [`vnode`] is a part.
259 pub vdom: &'a Shared<VDom>,
260}