feat: component event callbacks + function prop forwarding
- ComponentUse wraps Assign/MethodCall/Block props as arrow functions - Component prop wrapper preserves function props (typeof check) - Event handler fallthrough calls function-type identifiers - New: examples/callback-demo.ds with Button onClick callbacks - All existing examples build successfully
This commit is contained in:
parent
cbd6dfc7a6
commit
9d01f1b702
2 changed files with 47 additions and 6 deletions
|
|
@ -467,12 +467,12 @@ impl JsEmitter {
|
||||||
self.emit_line(&format!("function DS_{}(props, __children) {{", comp.name));
|
self.emit_line(&format!("function DS_{}(props, __children) {{", comp.name));
|
||||||
self.indent += 1;
|
self.indent += 1;
|
||||||
// Destructure props into local signal-compatible variables
|
// Destructure props into local signal-compatible variables
|
||||||
// Props may be raw values or signals — wrap in a signal-like accessor
|
// Props may be raw values, signals, or callback functions
|
||||||
for p in &comp.props {
|
for p in &comp.props {
|
||||||
// Create a signal-like wrapper: if prop is already a signal, use it; otherwise wrap
|
// Create a signal-like wrapper: if prop is a function, keep as-is; if already a signal, use it; otherwise wrap
|
||||||
self.emit_line(&format!(
|
self.emit_line(&format!(
|
||||||
"const {} = (props.{} !== undefined && props.{} !== null) ? (typeof props.{} === 'object' && 'value' in props.{} ? props.{} : {{ get value() {{ return props.{}; }} }}) : {{ get value() {{ return \"\"; }} }};",
|
"const {} = (typeof props.{} === 'function') ? props.{} : (props.{} !== undefined && props.{} !== null) ? (typeof props.{} === 'object' && 'value' in props.{} ? props.{} : {{ get value() {{ return props.{}; }} }}) : {{ get value() {{ return \"\"; }} }};",
|
||||||
p.name, p.name, p.name, p.name, p.name, p.name, p.name
|
p.name, p.name, p.name, p.name, p.name, p.name, p.name, p.name, p.name
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let root_id = self.emit_view_expr(&comp.body, graph);
|
let root_id = self.emit_view_expr(&comp.body, graph);
|
||||||
|
|
@ -901,7 +901,18 @@ impl JsEmitter {
|
||||||
Expr::ComponentUse { name, props, children } => {
|
Expr::ComponentUse { name, props, children } => {
|
||||||
let node_var = self.next_node_id();
|
let node_var = self.next_node_id();
|
||||||
let props_js: Vec<String> = props.iter()
|
let props_js: Vec<String> = props.iter()
|
||||||
.map(|(k, v)| format!("{}: {}", k, self.emit_expr(v)))
|
.map(|(k, v)| {
|
||||||
|
// Detect callback props (assignments, method calls, blocks)
|
||||||
|
let is_callback = matches!(v,
|
||||||
|
Expr::Assign(_, _, _) | Expr::MethodCall(_, _, _) | Expr::Block(_)
|
||||||
|
);
|
||||||
|
if is_callback {
|
||||||
|
let handler_js = self.emit_event_handler_expr(v);
|
||||||
|
format!("{}: () => {{ {}; DS.flush() }}", k, handler_js)
|
||||||
|
} else {
|
||||||
|
format!("{}: {}", k, self.emit_expr(v))
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
if children.is_empty() {
|
if children.is_empty() {
|
||||||
self.emit_line(&format!("const {} = DS_{}({{ {} }});", node_var, name, props_js.join(", ")));
|
self.emit_line(&format!("const {} = DS_{}({{ {} }});", node_var, name, props_js.join(", ")));
|
||||||
|
|
@ -1344,7 +1355,14 @@ impl JsEmitter {
|
||||||
let stmts: Vec<String> = exprs.iter().map(|e| self.emit_event_handler_expr(e)).collect();
|
let stmts: Vec<String> = exprs.iter().map(|e| self.emit_event_handler_expr(e)).collect();
|
||||||
stmts.join("; ")
|
stmts.join("; ")
|
||||||
}
|
}
|
||||||
_ => self.emit_expr(expr),
|
_ => {
|
||||||
|
// For plain identifiers that might be callback props, call them
|
||||||
|
if let Expr::Ident(name) = expr {
|
||||||
|
format!("if (typeof {} === 'function') {}()", name, name)
|
||||||
|
} else {
|
||||||
|
self.emit_expr(expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
23
examples/callback-demo.ds
Normal file
23
examples/callback-demo.ds
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
-- DreamStack Callback Demo
|
||||||
|
-- Tests component event callbacks
|
||||||
|
|
||||||
|
import { Card } from "../registry/components/card"
|
||||||
|
import { Button } from "../registry/components/button"
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
let message = "Click a button"
|
||||||
|
|
||||||
|
view main = column [
|
||||||
|
text "🔗 Component Callbacks" { variant: "title" }
|
||||||
|
text "Components can trigger parent actions" { variant: "subtitle" }
|
||||||
|
|
||||||
|
Card { title: "Using Button Component", subtitle: "callback props" } [
|
||||||
|
text "Count: {count}" { variant: "title" }
|
||||||
|
text message { variant: "subtitle" }
|
||||||
|
row [
|
||||||
|
Button { label: "+1", onClick: count += 1, variant: "primary" }
|
||||||
|
Button { label: "-1", onClick: count -= 1, variant: "secondary" }
|
||||||
|
Button { label: "Reset", onClick: count = 0, variant: "ghost" }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
Loading…
Add table
Reference in a new issue