Crates.io | respo |
lib.rs | respo |
version | |
source | src |
created_at | 2022-05-15 19:41:07.907296 |
updated_at | 2024-12-10 19:49:45.760771 |
description | a tiny virtual DOM library migrated from ClojureScript |
homepage | https://respo-mvc.org |
repository | https://github.com/Respo/respo.rs |
max_upload_size | |
id | 587310 |
Cargo.toml error: | TOML parse error at line 17, column 1 | 17 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
tiny toy virtual DOM based framework for Rust.
Status: experimenting, unhappy without HMR.
Respo was initially designed to work in a dynamic language with persistent data and HMR(hot code replacement), which is dramatically different from Rust. So this is more like an experiment.
Here is some preview of DOM syntax:
Ok(
div()
.class(ui_global())
.style(respo_style().padding(12.0))
.children([
comp_counter(&states.pick("counter"), store.counted)?,
comp_panel(&states.pick("panel"))?,
comp_todolist(memo_caches, &states.pick("todolist"), &store.tasks)?,
]),
)
CSS-in-Rust:
static_styles!(
style_remove_button,
(
"&",
respo_style()
.width(16.px())
.height(16.px())
.margin(4.)
.cursor("pointer")
.margin4(0.0, 0.0, 0.0, 16.0)
.color(CssColor::Hsl(0, 90, 90)),
),
("&:hover", respo_style().color(CssColor::Hsl(0, 90, 80))),
);
Builtin styles, demonstrated:
function | usages |
---|---|
ui_global |
global styles |
ui_fullscreen |
fullscreen styles |
ui_button |
button styles |
ui_input |
input styles |
ui_textarea |
textarea styles |
ui_link |
link styles |
ui_flex |
flex:1 styles |
ui_expand |
flex:1 styles with scrolls |
ui_center |
flexbox center styles |
ui_row |
flexbox row styles |
ui_column |
flexbox column styles |
ui_row_center |
flexbox row center styles |
ui_column_center |
flexbox column center styles |
ui_row_around |
flexbox row around styles |
ui_column_around |
flexbox column around styles |
ui_row_evenly |
flexbox row evenly styles |
ui_column_evenly |
flexbox column evenly styles |
ui_row_parted |
flexbox row between styles |
ui_column_parted |
flexbox column between styles |
ui_row_middle |
flexbox row between styles |
ui_column_middle |
flexbox column between styles |
ui_font_code |
code font family |
ui_font_normal |
normal font family(Hind) |
ui_font_fancy |
fancy font family(Josefin Sans) |
There are several dialog components in the demo. Syntax is not nice enough, so I'm not advertising it. But they work relatively good.
For more components, read code in src/app/
, they are just variants like RespoNode::Component(..)
. It may be sugared in the future, not determined yet.
Declaring a store:
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Store {
pub states: RespoStatesTree,
// TODO you app data
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ActionOp {
// TODO
StatesChange(RespoUpdateState),
}
impl RespoAction for ActionOp {
type Intent = (); // Intent is optional, it's for async actions.
fn states_action(a: RespoUpdateState) -> Self {
Self::StatesChange(a)
}
}
impl RespoStore for Store {
type Action = ActionOp;
fn update(&mut self, op: Self::Action) -> Result<(), String> {
match op {
// TODO
}
Ok(())
}
}
Declaring an app:
struct App {
store: Rc<RefCell<Store>>,
mount_target: Node,
}
impl RespoApp for App {
type Model = Store;
fn get_store(&self) -> Rc<RefCell<Self::Model>> {
self.store.to_owned()
}
fn get_mount_target(&self) -> &web_sys::Node {
&self.mount_target
}
fn dispatch(store: &mut RefMut<Self::Model>, op: Self::Action) -> Result<(), String> {
store.update(op)
}
fn view(store: Ref<Self::Model>, memo_caches: MemoCache<RespoNode<Self::Action>>) -> Result<RespoNode<Self::Action>, String> {
let states = &store.states;
// util::log!("global store: {:?}", store);
Ok(
div()
.class(ui_global())
.style(respo_style().padding(12.0))
.children([
comp_counter(&states.pick("counter"), store.counted)?,
comp_panel(&states.pick("panel"))?,
comp_todolist(memo_caches, &states.pick("todolist"), &store.tasks)?,
]),
)
}
}
Mounting app:
let app = App {
mount_target: query_select_node(".app").expect("mount target"),
store: Rc::new(RefCell::new(Store {
counted: 0,
states: RespoStatesTree::default(),
tasks: vec![],
})),
};
app.render_loop().expect("app render");
Apache License 2.0 .