Skip to main content

rust_elm/
component.rs

1use crate::cmd::Cmd;
2
3/// Scoped child slot — lifts child update into parent state/action space.
4pub struct Slot<ParentState, ParentAction, ChildState, ChildAction> {
5    pub get: fn(&ParentState) -> Option<&ChildState>,
6    pub get_mut: fn(&mut ParentState) -> Option<&mut ChildState>,
7    pub embed: fn(ChildAction) -> ParentAction,
8    pub child_update: fn(&mut ChildState, ChildAction) -> Cmd<ChildAction>,
9}
10
11impl<PS, PA: Send + 'static, CS, CA: Send + 'static> Slot<PS, PA, CS, CA> {
12    pub fn new(
13        get: fn(&PS) -> Option<&CS>,
14        get_mut: fn(&mut PS) -> Option<&mut CS>,
15        embed: fn(CA) -> PA,
16        child_update: fn(&mut CS, CA) -> Cmd<CA>,
17    ) -> Self {
18        Self {
19            get,
20            get_mut,
21            embed,
22            child_update,
23        }
24    }
25
26    pub fn lift(&self, parent: &mut PS, action: PA, extract: fn(PA) -> Option<CA>) -> Cmd<PA> {
27        let Some(child_action) = extract(action) else {
28            return Cmd::none();
29        };
30        let Some(child) = (self.get_mut)(parent) else {
31            return Cmd::none();
32        };
33        (self.child_update)(child, child_action).map(self.embed)
34    }
35}
36
37/// Lift a child `Cmd` into the parent action space.
38pub fn lift<PA: Send + 'static, CA: Send + 'static>(
39    cmd: Cmd<CA>,
40    embed: fn(CA) -> PA,
41) -> Cmd<PA> {
42    cmd.map(embed)
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[derive(Default)]
50    struct Parent {
51        child: Option<Child>,
52    }
53
54    #[derive(Default)]
55    struct Child {
56        n: i32,
57    }
58
59    #[derive(Clone, Copy)]
60    enum PMsg {
61        Child(CMsg),
62    }
63
64    #[derive(Clone, Copy)]
65    enum CMsg {
66        Inc,
67    }
68
69    fn child_update(c: &mut Child, msg: CMsg) -> Cmd<CMsg> {
70        match msg {
71            CMsg::Inc => c.n += 1,
72        }
73        Cmd::none()
74    }
75
76    #[test]
77    fn slot_lift_updates_nested_state() {
78        let slot = Slot::new(
79            |p: &Parent| p.child.as_ref(),
80            |p: &mut Parent| p.child.as_mut(),
81            |c| PMsg::Child(c),
82            child_update,
83        );
84        let mut parent = Parent {
85            child: Some(Child::default()),
86        };
87        let extract = |m: PMsg| match m {
88            PMsg::Child(c) => Some(c),
89        };
90        let _ = slot.lift(&mut parent, PMsg::Child(CMsg::Inc), extract);
91        assert_eq!(parent.child.unwrap().n, 1);
92    }
93}