///|
using @cmd {trait Scheduler}
///|
#warnings("-unused_constructor")
pub enum VNode {
Elem(String, Props, Children[VNode], namespace_uri~ : String?)
Text(String)
Frag(Array[VNode])
Thunk(Int, () -> VNode)
}
///|
pub impl Eq for VNode with fn equal(a, b) {
physical_equal(a, b)
}
///|
const CAPTURED_LINK_TAG : String = "RABBITA_CAPTURED_LINK"
///|
const FRAGMENT_START_MARKER : String = "["
///|
const FRAGMENT_END_MARKER : String = "]"
///|
fn rendered_tag(tag : String) -> String {
if tag == CAPTURED_LINK_TAG {
"a"
} else {
tag
}
}
///|
#cfg(target="js")
fn is_captured_link_tag(tag : String) -> Bool {
tag == CAPTURED_LINK_TAG
}
///|
fn is_html_void_element(tag : String) -> Bool {
match tag {
"area"
| "base"
| "br"
| "col"
| "embed"
| "hr"
| "img"
| "input"
| "link"
| "meta"
| "param"
| "source"
| "track"
| "wbr" => true
_ => false
}
}
///|
pub fn VNode::elem(
tag : String,
props : Props,
children : Children[VNode],
namespace_uri? : String,
) -> VNode {
Elem(tag, props, children, namespace_uri~)
}
///|
pub fn VNode::text(s : String) -> VNode {
Text(s)
}
///|
pub fn VNode::link(
props : Props,
children : Children[VNode],
escape? : Bool = false,
) -> VNode {
let tag = if escape { "a" } else { CAPTURED_LINK_TAG }
VNode::elem(tag, props, children)
}
///|
pub fn VNode::fragment(childs : Array[VNode]) -> VNode {
Frag(childs)
}
///|
pub fn VNode::document(props : Props, head : VNode, body : VNode) -> VNode {
Elem("html", props, Array([head, body]), namespace_uri=None)
}
///|
pub(all) enum Children[T] {
Array(Array[T])
Map(Map[String, T])
RawHtml(String)
}
///|
/// ★ moobile 改造:事件值是**后端无关的不透明类型**,不再别名为 `@dom.Event`。
///
/// 为什么必须改:原来的别名让 `Props.handlers` 的**公开签名**带着 `@dom`,
/// 而 `@dom` 是 js-only 的 DOM 绑定包。后果是外部包既命名不了这个类型、
/// 也换不掉它 —— 这正是当初不得不加 `Props::each_handler` 的原因。
///
/// 现在:DOM 后端在监听器边界处把 `@dom.Event` 强转进来(`dom_event`),
/// React 后端直接把合成事件透传。语义上两边都是"一个事件值",
/// 谁也不需要认识对方的具体类型。
#external
pub type Event
///|
/// `@dom.Event` → `Event`。DOM 后端在监听器边界处使用。
#cfg(target="js")
fn dom_event(value : @dom.Event) -> Event = "%identity"
///|
/// `Event` → `@dom.Event`。DOM 侧的辅助函数(从事件里取值)使用。
#cfg(target="js")
pub fn as_dom_event(value : Event) -> @dom.Event = "%identity"
///|
pub struct Props {
handlers : Map[String, (Event, &Scheduler) -> Unit]
attrs : Map[String, String]
props : Map[String, @variant.Variant]
// ★ moobile 改造:样式值从一开始就是**类型化**的,不再是"待解析的字符串"。
// 旧的 `Map[String, String]` 通道已删除 —— 实测零调用点。
styles : Map[String, @style.StyleValue]
}
///|
pub fn Props::new(
attrs : Map[String, String],
props : Map[String, @variant.Variant],
styles : Map[String, @style.StyleValue],
handlers : Map[String, (Event, &Scheduler) -> Unit],
) -> Props {
{ handlers, attrs, styles, props }
}
///|
fn[K : Eq + Hash, V] copy_map(src : Map[K, V]) -> Map[K, V] {
let dst : Map[K, V] = Map([])
for k, v in src {
dst[k] = v
}
dst
}
///|
pub fn Props::copy(self : Props) -> Props {
{
handlers: copy_map(self.handlers),
attrs: copy_map(self.attrs),
props: copy_map(self.props),
styles: copy_map(self.styles),
}
}
// ============================================================================
// ★ moobile 扩展(fork 的一部分)
//
// 存在的理由:`Event` 在本包内**不是 pub**,`Attrs::handler` 也是私有的,
// 所以外部包既没法给处理器标注类型、也没法注册处理器。
// 这三个方法把 `Event` 藏在签名里,调用方靠类型推断即可使用:
//
// props.on("click", _ => dispatch(Msg::Inc))
//
// 这正是 README 说的「fork 的第二档:改造 rabbita 的类型支持」。
// ============================================================================
///|
/// 空属性集。
pub fn Props::empty() -> Props {
{ handlers: Map([]), attrs: Map([]), props: Map([]), styles: Map([]) }
}
///|
/// 合并一份**类型化**样式。
pub fn Props::styles(self : Props, s : @style.Style) -> Props {
for entry in s.entries() {
self.styles[entry.0] = entry.1
}
self
}
///|
/// 注册事件处理器。`Event` 由签名提供,调用方不必命名它。
pub fn Props::on(
self : Props,
event : String,
f : (Event, &Scheduler) -> Unit,
) -> Props {
self.handlers[event] = f
self
}
///|
/// 设置字符串属性(`placeholder` / `value` 这类)。
pub fn Props::attr(self : Props, key : String, value : String) -> Props {
self.attrs[key] = value
self
}
///|
/// 设置非字符串属性(bool / int / float)。
pub fn Props::prop(self : Props, key : String, value : @variant.Variant) -> Props {
self.props[key] = value
self
}
///|
/// 类型化样式表(只读)。
pub fn Props::styles_map(self : Props) -> Map[String, @style.StyleValue] {
self.styles
}
///|
/// 字符串属性表(只读)。
pub fn Props::attrs_map(self : Props) -> Map[String, String] {
self.attrs
}
///|
/// 非字符串属性表(只读)。
pub fn Props::props_map(self : Props) -> Map[String, @variant.Variant] {
self.props
}
///|
/// 把事件处理器包成"只吃一个 JS 值"的形式,供非 DOM 后端(React Native)调用。
pub fn Props::each_handler(
self : Props,
scheduler : &Scheduler,
f : (String, (Event) -> Unit) -> Unit,
) -> Unit {
for event, handler in self.handlers {
f(event, (v) => handler(v, scheduler))
}
}