///|
#cfg(target="js")
struct VDom {
mut inode : INode
target_element : @dom.Element?
captured_link_listener : @dom.Listener
}
///|
#cfg(not(target="js"))
pub struct VDom {
mut vnode : VNode
}
///|
#cfg(target="js")
priv struct IProps {
props : Props
slots : Map[String, Ref[(Event, &Scheduler) -> Unit]]
}
///|
#cfg(target="js")
priv enum INode {
Elem(String, IProps, Children[INode], namespace_uri~ : String?, @dom.Element)
Text(String, @dom.Element)
Frag(Array[INode], @dom.Comment, @dom.Comment)
Thunk(Int, INode)
}
///|
#cfg(target="js")
fn INode::start(self : Self) -> @dom.Node {
match self {
Elem(_, _, _, element, ..) | Text(_, element) => element.as_node()
Thunk(_, node) => node.start()
Frag(_, start, _) => start.as_node()
}
}
///|
#cfg(target="js")
fn INode::end(self : Self) -> @dom.Node {
match self {
Elem(_, _, _, element, ..) | Text(_, element) => element.as_node()
Thunk(_, node) => node.end()
Frag(_, _, end) => end.as_node()
}
}
///|
#cfg(target="js")
fn INode::needs_relocation(
self : Self,
anchor : @js.Nullable[@dom.Node],
) -> Bool {
match (self.end().get_next_sibling().to_option(), anchor.to_option()) {
(None, None) => false
(Some(actual), Some(expected)) => !actual.is_same_node(expected)
_ => true
}
}
///|
#cfg(target="js")
fn INode::remove(self : Self, parent : @dom.Node) -> Unit {
match self {
Elem(_, _, _, element, ..) | Text(_, element) =>
parent.remove_child(element.as_node())
Thunk(_, node) => node.remove(parent)
Frag(_, start, end) => {
let start = start.as_node()
let end = end.as_node()
while start.get_next_sibling().to_option() is Some(node) &&
!node.is_same_node(end) {
parent.remove_child(node)
}
parent.remove_child(start)
parent.remove_child(end)
}
}
}
///|
#cfg(target="js")
fn INode::relocate(
self : Self,
parent : @dom.Node,
before : @js.Nullable[@dom.Node],
) -> Unit {
guard self.needs_relocation(before) else { return }
match self {
Elem(_, _, _, element, ..) | Text(_, element) =>
parent.insert_before(element.as_node(), before)
Thunk(_, node) => node.relocate(parent, before)
Frag(_, start, end) => {
{
guard! start.get_parent_node().to_option() is Some(a)
guard! end.get_parent_node().to_option() is Some(b)
guard! a.is_same_node(b)
}
let start = start.as_node()
let end = end.as_node()
let mut anchor = before
while end.get_previous_sibling().to_option() is Some(node) &&
!node.is_same_node(start) {
parent.insert_before(node, anchor)
anchor = nullable(node)
}
// For an empty fragment, anchor equals before: keep start before end.
parent.insert_before(start, anchor)
parent.insert_before(end, before)
}
}
}
///|
#cfg(target="js")
fn insert_props(
element : @dom.Element,
tag : String,
props : Props,
scheduler : &Scheduler,
captured_link_listener : @dom.Listener,
) -> IProps {
let { handlers, attrs, props: properties, styles, } = props
let slots : Map[String, Ref[(Event, &Scheduler) -> Unit]] = Map([])
for event, handler in handlers {
let slot = @ref.new(handler)
element.add_event_listener(event, event => (slot.val)(dom_event(event), scheduler))
slots[event] = slot
}
if is_captured_link_tag(tag) {
element.add_event_listener("click", captured_link_listener)
}
for name, value in attrs {
element.set_attribute(name, value)
}
for name, value in properties {
element.set_property(name, variant_to_js_value(value))
}
let sheet = if element.to_html_element() is Some(html) {
html.get_style()
} else if element.to_svg_element() is Some(svg) {
svg.get_style()
} else {
panic()
}
for name, value in styles {
// 类型化样式 → CSSOM。camelCase 键要转成 CSS 语法,长度要补 px。
sheet.set_property(@style.css_property_name(name), value.to_css())
}
{ props, slots, }
}
///|
#cfg(target="js")
fn reapply_child_sensitive_properties(
element : @dom.Element,
tag : String,
props : Props,
) -> Unit {
// Adding