///|
priv enum AttrSlotKind {
AttributeSlot
PropertySlot
EventSlot
StyleSlot
} derive(Eq)
///|
fn[Msg] bind_attr(attr : Attr[Msg], dispatch : (Msg) -> Unit) -> Attr[Unit] {
match attr {
Attribute(k, v) => Attribute(k, v)
Property(k, v) => Property(k, v)
Event(name, handler) => BoundEvent(name, fn(e) { dispatch(handler(e)) })
EventMaybe(name, handler) =>
BoundEvent(name, fn(e) {
match handler(e) {
Some(msg) => dispatch(msg)
None => ()
}
})
BoundEvent(name, handler) => BoundEvent(name, handler)
Style(k, v) => Style(k, v)
}
}
///|
fn[Msg] attr_slot_kind(attr : Attr[Msg]) -> AttrSlotKind {
match attr {
Attribute(_, _) => AttributeSlot
Property(_, _) => PropertySlot
Event(_, _) => EventSlot
EventMaybe(_, _) => EventSlot
BoundEvent(_, _) => EventSlot
Style(_, _) => StyleSlot
}
}
///|
fn[Msg] attr_name(attr : Attr[Msg]) -> String {
match attr {
Attribute(name, _) => name
Property(name, _) => name
Event(name, _) => name
EventMaybe(name, _) => name
BoundEvent(name, _) => name
Style(name, _) => name
}
}
///|
fn[Msg] same_attr_slot(old : Attr[Msg], new_ : Attr[Msg]) -> Bool {
attr_slot_kind(old) == attr_slot_kind(new_) &&
attr_name(old) == attr_name(new_)
}
///|
fn[Msg] attr_needs_update(old : Attr[Msg], new_ : Attr[Msg]) -> Bool {
match (old, new_) {
(Attribute(_, old_value), Attribute(_, new_value)) => old_value != new_value
(Style(_, old_value), Style(_, new_value)) => old_value != new_value
// Properties, events, and bound events: always re-apply (not comparable)
(Property(_, _), Property(_, _))
| (Event(_, _), Event(_, _))
| (EventMaybe(_, _), EventMaybe(_, _))
| (BoundEvent(_, _), BoundEvent(_, _)) => true
// Cross-type mismatch: always needs update.
// Enumerating old variants ensures a new Attr variant triggers a compile error.
(Attribute(_, _), _)
| (Property(_, _), _)
| (Event(_, _), _)
| (EventMaybe(_, _), _)
| (BoundEvent(_, _), _)
| (Style(_, _), _) => true
}
}
///|
/// Set a DOM Level 0 event handler (e.g., el.onclick = fn)
fn set_event_handler(
el : @webapi.Element,
name : String,
handler : (@webapi.Event) -> Unit,
) -> Unit {
set_prop(el, "on" + name, @webapi.EventListener::new(handler).to_js())
}
///|
/// Remove a DOM Level 0 event handler (e.g., el.onclick = null)
fn remove_event_handler(el : @webapi.Element, name : String) -> Unit {
set_prop(el, "on" + name, @webapi.JsValue::null())
}
///|
fn set_prop(el : @webapi.Element, name : String, val : @webapi.JsValue) -> Unit {
let obj : @webapi.JsObject = @webapi.TJsValue::to_js(el).unsafe_into()
obj.set(name, val) |> ignore
}
///|
fn set_style_prop(el : @webapi.Element, name : String, value : String) -> Unit {
let html_el : @webapi.HTMLElement = @webapi.TJsValue::to_js(el).unsafe_into()
html_el.style().set_property(name, value)
}
///|
fn apply_bound_attr(el : @webapi.Element, attr : Attr[Unit]) -> Unit {
match attr {
Attribute(name, value) => el.set_attribute(name, value)
Property(name, value) => set_prop(el, name, value)
BoundEvent(name, handler) => set_event_handler(el, name, handler)
Style(name, value) => set_style_prop(el, name, value)
Event(_, _) =>
abort("chai: apply_bound_attr expected a bound event handler")
EventMaybe(_, _) =>
abort("chai: apply_bound_attr expected a bound event handler")
}
}
///|
fn[Msg] apply_attr(
el : @webapi.Element,
attr : Attr[Msg],
dispatch : (Msg) -> Unit,
) -> Unit {
apply_bound_attr(el, bind_attr(attr, dispatch))
}