|
| 1 | +// SPDX-License-Identifier: MPL-2.0 |
| 2 | +// Dom.affine — minimal virtual-DOM bindings for the Error-Lang Studio IDE. |
| 3 | +// |
| 4 | +// Written fresh for error-lang (MPL-2.0). This is NOT derived from |
| 5 | +// hyperpolymath/affinescript-dom, which is AGPL-3.0-or-later and therefore |
| 6 | +// licence-incompatible with this MPL-2.0 estate. The pieces here are (a) FFI |
| 7 | +// `extern fn` declarations over the host DOM shim (ide/src/*.js) — function |
| 8 | +// signatures, not copyrightable expression — and (b) a small, independently |
| 9 | +// authored vnode builder + realiser. |
| 10 | +// |
| 11 | +// Event model (affine / host-runtime friendly): a vnode carries event bindings |
| 12 | +// as (event-name, handler-id) string pairs rather than typed closures. The host |
| 13 | +// bridge (Main) maps a handler-id back to a Msg and feeds the TEA run loop, so |
| 14 | +// the View stays a pure `Model -> VNode` function with no embedded closures. |
| 15 | + |
| 16 | +module Dom; |
| 17 | + |
| 18 | +use prelude::*; |
| 19 | + |
| 20 | +// ---- host DOM externs (opaque Int node handles) ---- |
| 21 | +pub extern fn dom_query_selector(selector: String) -> Int; |
| 22 | +pub extern fn dom_create_element(tag: String) -> Int; |
| 23 | +pub extern fn dom_create_text_node(content: String) -> Int; |
| 24 | +pub extern fn dom_append_child(parent: Int, child: Int) -> Unit; |
| 25 | +pub extern fn dom_set_attribute(el: Int, name: String, value: String) -> Unit; |
| 26 | +pub extern fn dom_clear_children(el: Int) -> Unit; |
| 27 | +pub extern fn dom_bind_event(el: Int, event: String, handler_id: String) -> Unit; |
| 28 | + |
| 29 | +// ---- virtual nodes ---- |
| 30 | +// VElem(tag, attrs, events, children); events are (event-name, handler-id). |
| 31 | +pub enum VNode { |
| 32 | + VElem(String, [(String, String)], [(String, String)], [VNode]), |
| 33 | + VText(String) |
| 34 | +} |
| 35 | + |
| 36 | +pub fn text(content: String) -> VNode { |
| 37 | + VText(content) |
| 38 | +} |
| 39 | + |
| 40 | +pub fn h(tag: String, attrs: [(String, String)], children: [VNode]) -> VNode { |
| 41 | + VElem(tag, attrs, [], children) |
| 42 | +} |
| 43 | + |
| 44 | +// element with event bindings, e.g. h_on("button", [("class","btn")], [("click","stop")], [...]) |
| 45 | +pub fn h_on(tag: String, attrs: [(String, String)], events: [(String, String)], children: [VNode]) -> VNode { |
| 46 | + VElem(tag, attrs, events, children) |
| 47 | +} |
| 48 | + |
| 49 | +// ---- realise a vnode into a host DOM node ---- |
| 50 | +pub fn render(vnode: VNode) -> Int { |
| 51 | + match vnode { |
| 52 | + VText(s) => dom_create_text_node(s), |
| 53 | + VElem(tag, attrs, events, children) => { |
| 54 | + let el = dom_create_element(tag); |
| 55 | + for a in attrs { |
| 56 | + let (name, value) = a; |
| 57 | + dom_set_attribute(el, name, value); |
| 58 | + } |
| 59 | + for ev in events { |
| 60 | + let (name, hid) = ev; |
| 61 | + dom_bind_event(el, name, hid); |
| 62 | + } |
| 63 | + for c in children { |
| 64 | + dom_append_child(el, render(c)); |
| 65 | + } |
| 66 | + el |
| 67 | + } |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +// replace the contents of `selector` with `vnode` |
| 72 | +pub fn mount(selector: String, vnode: VNode) -> Unit { |
| 73 | + let root = dom_query_selector(selector); |
| 74 | + dom_clear_children(root); |
| 75 | + dom_append_child(root, render(vnode)) |
| 76 | +} |
0 commit comments