feat: port el-ui vessels — rename crates→vessels, add El source + manifests

This commit is contained in:
Will Anderson
2026-05-05 04:19:22 -05:00
parent b580a63540
commit faee6fdb25
145 changed files with 4050 additions and 12 deletions
@@ -0,0 +1,137 @@
//! macOS backend — AppKit bindings.
//!
//! Architecture: correct and complete. AppKit calls are stubs marked TODO.
//!
//! Each `PlatformNode` maps to an NSView:
//! element("div") → NSView
//! element("button") → NSButton
//! element("input") → NSTextField
//! text("...") → NSTextField (label mode)
use crate::{EventHandler, PlatformBackend, PlatformError, PlatformNode, PlatformResult};
pub struct MacosBackend;
impl MacosBackend {
pub fn new() -> Self {
Self
}
fn appkit_class(tag: &str) -> &'static str {
match tag {
"button" => "NSButton",
"input" => "NSTextField",
"textarea" => "NSTextView",
"img" => "NSImageView",
"ul" | "ol" => "NSTableView",
"nav" => "NSToolbar",
_ => "NSView",
}
}
}
impl Default for MacosBackend {
fn default() -> Self {
Self::new()
}
}
impl PlatformBackend for MacosBackend {
fn name(&self) -> &'static str {
"macos"
}
fn create_element(&self, tag: &str) -> PlatformResult<PlatformNode> {
let mut node = PlatformNode::element(tag);
// TODO: extern "C" { fn el_macos_create_view(class_name: *const c_char) -> usize; }
node.attributes
.push(crate::Attribute::new("data-appkit-class", Self::appkit_class(tag)));
Ok(node)
}
fn create_text(&self, content: &str) -> PlatformResult<PlatformNode> {
let mut node = PlatformNode::text(content);
// TODO: NSTextField in label mode with stringValue = content
node.attributes
.push(crate::Attribute::new("data-appkit-class", "NSTextField"));
Ok(node)
}
fn set_attribute(
&self,
node: &mut PlatformNode,
name: &str,
value: &str,
) -> PlatformResult<()> {
// TODO: map to AppKit property setters
node.attributes.retain(|a| a.name != name);
node.attributes.push(crate::Attribute::new(name, value));
Ok(())
}
fn remove_attribute(&self, node: &mut PlatformNode, name: &str) -> PlatformResult<()> {
node.attributes.retain(|a| a.name != name);
Ok(())
}
fn append_child(
&self,
parent: &mut PlatformNode,
child: PlatformNode,
) -> PlatformResult<()> {
// TODO: [parentView addSubview:childView]
parent.children.push(child);
Ok(())
}
fn remove_child(&self, parent: &mut PlatformNode, child_index: usize) -> PlatformResult<()> {
if child_index >= parent.children.len() {
return Err(PlatformError::Render(format!(
"macos: child index {} out of bounds",
child_index
)));
}
// TODO: [childView removeFromSuperview]
parent.children.remove(child_index);
Ok(())
}
fn replace_child(
&self,
parent: &mut PlatformNode,
index: usize,
new_child: PlatformNode,
) -> PlatformResult<()> {
if index >= parent.children.len() {
return Err(PlatformError::Render("macos: replace_child out of bounds".into()));
}
parent.children[index] = new_child;
Ok(())
}
fn bind_event(
&self,
node: &mut PlatformNode,
event: &str,
_handler: EventHandler,
) -> PlatformResult<()> {
// TODO: NSButton.target + NSButton.action pattern via FFI
node.attributes
.push(crate::Attribute::new(format!("data-appkit-event-{}", event), "bound"));
Ok(())
}
fn render_to_string(&self, node: &PlatformNode) -> PlatformResult<String> {
Ok(node.to_html())
}
fn mount(&self, _root: PlatformNode, _container_id: &str) -> PlatformResult<()> {
// TODO: find NSWindow or NSViewController and set root as contentView
Ok(())
}
fn patch(&self, _old: &PlatformNode, _new: &PlatformNode) -> PlatformResult<()> {
// TODO: diff and apply AppKit mutations
Ok(())
}
}