Skip to content

Commit

Permalink
Procedural class! macro (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
maciejhirsz authored May 5, 2024
1 parent f6ed313 commit d9e2910
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 113 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ members = [
]
resolver = "2"

[profile.dev]
opt-level = 3

[profile.release]
lto = "fat"
codegen-units = 1
strip = "symbols"
panic = "abort"

[profile.bench]
lto = "fat"
Expand Down
11 changes: 0 additions & 11 deletions crates/kobold/js/util.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
const fragmentDecorators = new WeakMap();

export function appendChild(n,c) { n.appendChild(c); }
export function appendBefore(n,i) { n.before(i); }
export function removeNode(n) { n.remove(); }
export function replaceNode(o,n) { o.replaceWith(n); }
export function emptyNode() { return document.createTextNode(""); }
export function fragment()
{
Expand All @@ -30,15 +26,8 @@ export function fragmentReplace(f,n)
f.appendChild(e);
f.insertBefore(b, f.firstChild);
}
export function setTextContent(n,t) { n.textContent = t; }
export function setAttribute(n,a,v) { n.setAttribute(a, v); }

export function setChecked(n,v) { if (n.checked !== v) n.checked = v; }
export function setClassName(n,v) { n.className = v; }
export function setInnerHTML(n,v) { n.innerHTML = v; }
export function setHref(n,v) { n.href = v; }
export function setStyle(n,v) { n.style = v; }
export function setValue(n,v) { n.value = v; }

export function addClass(n,v) { n.classList.add(v); }
export function removeClass(n,v) { n.classList.remove(v); }
Expand Down
90 changes: 74 additions & 16 deletions crates/kobold/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@ impl Deref for AttributeName {

impl Property<&str> for &AttributeName {
fn set(self, this: &Node, value: &str) {
internal::set_attr(this, self, value)
internal::obj(this).set_attr(self, value);
}
}

impl Property<f64> for &AttributeName {
fn set(self, this: &Node, value: f64) {
internal::set_attr_num(this, self, value)
internal::obj(this).set_attr_num(self, value)
}
}

impl Property<bool> for &AttributeName {
fn set(self, this: &Node, value: bool) {
internal::set_attr_bool(this, self, value)
internal::obj(this).set_attr_bool(self, value);
}
}

Expand All @@ -56,20 +56,27 @@ macro_rules! attribute {
$(
impl Property<$abi> for $name {
fn set(self, this: &Node, value: $abi) {
internal::$util(this, value)
internal::obj(this).$util(value);
}
}
)*
)*
}
}

/// The `checked` attribute: <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#checked>
pub struct Checked;

impl Property<bool> for Checked {
fn set(self, this: &Node, value: bool) {
internal::checked(this, value);
}
}

/// The `Element.classList` property: <https://developer.mozilla.org/en-US/docs/Web/API/Element/classList>
pub struct Class;

attribute!(
/// The `checked` attribute: <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#checked>
Checked [checked: bool]
/// The `className` attribute: <https://developer.mozilla.org/en-US/docs/Web/API/Element/className>
ClassName [class_name: &str]
/// The `innerHTML` attribute: <https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML>
Expand Down Expand Up @@ -234,21 +241,72 @@ impl Attribute<Class> for String {
}

#[derive(Clone, Copy)]
pub struct OptionalClass {
class: &'static str,
pub struct StaticClass<T> {
toggle: T,
on: bool,
}

impl AsRef<str> for OptionalClass {
fn as_ref(&self) -> &str {
if self.on {
self.class
} else {
""
impl<T> StaticClass<T> {
pub const fn new(toggle: T, on: bool) -> Self
where
T: Fn(&Node, bool),
{
StaticClass { toggle, on }
}
}

impl<T> Attribute<Class> for StaticClass<T>
where
T: Fn(&Node, bool),
{
type Product = bool;

fn build(self) -> bool {
self.on
}

fn build_in(self, _: Class, node: &Node) -> bool {
(self.toggle)(node, self.on);
self.on
}

fn update_in(self, _: Class, node: &Node, memo: &mut bool) {
if self.on != *memo {
(self.toggle)(node, self.on);
*memo = self.on;
}
}
}

impl<T> Attribute<ClassName> for StaticClass<T>
where
T: Fn(&Node, bool),
{
type Product = bool;

fn build(self) -> bool {
self.on
}

fn build_in(self, _: ClassName, node: &Node) -> bool {
(self.toggle)(node, self.on);
self.on
}

fn update_in(self, _: ClassName, node: &Node, memo: &mut bool) {
if self.on != *memo {
(self.toggle)(node, self.on);
*memo = self.on;
}
}
}

#[derive(Clone, Copy)]
pub struct OptionalClass {
class: &'static str,
on: bool,
}

impl OptionalClass {
pub const fn new(class: &'static str, on: bool) -> Self {
OptionalClass { class, on }
Expand Down Expand Up @@ -286,14 +344,14 @@ impl Attribute<ClassName> for OptionalClass {

fn build_in(self, _: ClassName, node: &Node) -> bool {
if self.on {
internal::class_name(node, self.class);
internal::obj(node).class_name(self.class);
}
self.on
}

fn update_in(self, _: ClassName, node: &Node, memo: &mut bool) {
if self.on != *memo {
internal::class_name(node, self.as_ref());
internal::obj(node).class_name(self.class);
*memo = self.on;
}
}
Expand Down
12 changes: 6 additions & 6 deletions crates/kobold/src/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,19 @@ pub(crate) struct TextContent;

impl Property<&str> for TextContent {
fn set(self, this: &Node, value: &str) {
internal::set_text(this, value)
internal::obj(this).set_text(value);
}
}

impl Property<f64> for TextContent {
fn set(self, this: &Node, value: f64) {
internal::set_text_num(this, value)
internal::obj(this).set_text_num(value);
}
}

impl Property<bool> for TextContent {
fn set(self, this: &Node, value: bool) {
internal::set_text_bool(this, value)
internal::obj(this).set_text_bool(value);
}
}

Expand All @@ -118,7 +118,7 @@ impl FragmentBuilder {
}

pub fn append(&self, child: &JsValue) {
internal::append_before(&self.tail, child);
internal::obj(&self.tail).append_before(child);
}
}

Expand All @@ -138,11 +138,11 @@ impl Mountable for Node {
}

fn unmount(&self) {
internal::unmount(self)
internal::obj(self).unmount();
}

fn replace_with(&self, new: &JsValue) {
internal::replace(self, new)
internal::obj(self).replace(new);
}
}

Expand Down
87 changes: 48 additions & 39 deletions crates/kobold/src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ where

#[wasm_bindgen]
extern "C" {
pub(crate) type UnsafeNode;

#[wasm_bindgen(js_namespace = ["document", "body"], js_name = appendChild)]
pub(crate) fn append_body(node: &JsValue);
#[wasm_bindgen(js_namespace = document, js_name = createTextNode)]
Expand All @@ -222,6 +224,52 @@ extern "C" {
pub(crate) fn text_node_num(t: f64) -> Node;
#[wasm_bindgen(js_namespace = document, js_name = createTextNode)]
pub(crate) fn text_node_bool(t: bool) -> Node;

// dom manipulation ----------------

#[wasm_bindgen(method, js_name = "before")]
pub(crate) fn append_before(this: &UnsafeNode, insert: &JsValue);
#[wasm_bindgen(method, js_name = "remove")]
pub(crate) fn unmount(this: &UnsafeNode);
#[wasm_bindgen(method, js_name = "replaceWith")]
pub(crate) fn replace(this: &UnsafeNode, new: &JsValue);

// `set_text` variants ----------------

#[wasm_bindgen(method, setter, js_name = "textContent")]
pub(crate) fn set_text(this: &UnsafeNode, t: &str);
#[wasm_bindgen(method, setter, js_name = "textContent")]
pub(crate) fn set_text_num(this: &UnsafeNode, t: f64);
#[wasm_bindgen(method, setter, js_name = "textContent")]
pub(crate) fn set_text_bool(this: &UnsafeNode, t: bool);

// `set_attr` variants ----------------

#[wasm_bindgen(method, js_name = "setAttribute")]
pub(crate) fn set_attr(this: &UnsafeNode, a: &str, v: &str);
#[wasm_bindgen(method, js_name = "setAttribute")]
pub(crate) fn set_attr_num(this: &UnsafeNode, a: &str, v: f64);
#[wasm_bindgen(method, js_name = "setAttribute")]
pub(crate) fn set_attr_bool(this: &UnsafeNode, a: &str, v: bool);

// provided attribute setters ----------------

#[wasm_bindgen(method, setter, js_name = "className")]
pub(crate) fn class_name(this: &UnsafeNode, value: &str);
#[wasm_bindgen(method, setter, js_name = "innerHTML")]
pub(crate) fn inner_html(this: &UnsafeNode, value: &str);
#[wasm_bindgen(method, setter, js_name = "href")]
pub(crate) fn href(this: &UnsafeNode, value: &str);
#[wasm_bindgen(method, setter, js_name = "style")]
pub(crate) fn style(this: &UnsafeNode, value: &str);
#[wasm_bindgen(method, setter, js_name = "value")]
pub(crate) fn value(this: &UnsafeNode, value: &str);
#[wasm_bindgen(method, setter, js_name = "value")]
pub(crate) fn value_num(this: &UnsafeNode, value: f64);
}

pub(crate) fn obj(node: &Node) -> &UnsafeNode {
node.unchecked_ref()
}

mod hidden {
Expand All @@ -237,15 +285,6 @@ mod hidden {

#[wasm_bindgen(module = "/js/util.js")]
extern "C" {
#[wasm_bindgen(js_name = "appendChild")]
pub(crate) fn append_child(parent: &Node, child: &JsValue);
#[wasm_bindgen(js_name = "appendBefore")]
pub(crate) fn append_before(node: &Node, insert: &JsValue);
#[wasm_bindgen(js_name = "removeNode")]
pub(crate) fn unmount(node: &JsValue);
#[wasm_bindgen(js_name = "replaceNode")]
pub(crate) fn replace(old: &JsValue, new: &JsValue);

#[wasm_bindgen(js_name = "emptyNode")]
pub(crate) fn empty_node() -> Node;
#[wasm_bindgen(js_name = "fragment")]
Expand All @@ -257,40 +296,10 @@ extern "C" {
#[wasm_bindgen(js_name = "fragmentReplace")]
pub(crate) fn fragment_replace(f: &Node, new: &JsValue);

// `set_text` variants ----------------

#[wasm_bindgen(js_name = "setTextContent")]
pub(crate) fn set_text(el: &Node, t: &str);
#[wasm_bindgen(js_name = "setTextContent")]
pub(crate) fn set_text_num(el: &Node, t: f64);
#[wasm_bindgen(js_name = "setTextContent")]
pub(crate) fn set_text_bool(el: &Node, t: bool);

// `set_attr` variants ----------------

#[wasm_bindgen(js_name = "setAttribute")]
pub(crate) fn set_attr(el: &JsValue, a: &str, v: &str);
#[wasm_bindgen(js_name = "setAttribute")]
pub(crate) fn set_attr_num(el: &JsValue, a: &str, v: f64);
#[wasm_bindgen(js_name = "setAttribute")]
pub(crate) fn set_attr_bool(el: &JsValue, a: &str, v: bool);

// provided attribute setters ----------------

#[wasm_bindgen(js_name = "setChecked")]
pub(crate) fn checked(node: &Node, value: bool);
#[wasm_bindgen(js_name = "setClassName")]
pub(crate) fn class_name(node: &Node, value: &str);
#[wasm_bindgen(js_name = "setInnerHTML")]
pub(crate) fn inner_html(node: &Node, value: &str);
#[wasm_bindgen(js_name = "setHref")]
pub(crate) fn href(node: &Node, value: &str);
#[wasm_bindgen(js_name = "setStyle")]
pub(crate) fn style(node: &Node, value: &str);
#[wasm_bindgen(js_name = "setValue")]
pub(crate) fn value(node: &Node, value: &str);
#[wasm_bindgen(js_name = "setValue")]
pub(crate) fn value_num(node: &Node, value: f64);

// ----------------

Expand Down
12 changes: 1 addition & 11 deletions crates/kobold/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@
pub use kobold_macros::component;

/// Macro for creating transient [`View`] types. See the [main documentation](crate) for details.
pub use kobold_macros::view;
pub use kobold_macros::{class, view};

use wasm_bindgen::JsCast;

Expand Down Expand Up @@ -572,16 +572,6 @@ fn init_panic_hook() {
}
}

#[macro_export]
macro_rules! class {
($class:literal if $on:expr) => {
::kobold::attribute::OptionalClass::new($class, $on)
};
($class:tt if $on:expr) => {
::kobold::attribute::OptionalClass::new($class, $on)
};
}

/// Binds a closure to a given [`Hook`](stateful::Hook). In practice:
///
/// ```
Expand Down
Loading

0 comments on commit d9e2910

Please sign in to comment.