1use std::collections::HashMap;
7
8use crate::component::{Component, ComponentError, ComponentId, Context, Node};
9
10pub trait RenderProp<T> {
12 fn render(&self, data: T) -> Result<Vec<Node>, ComponentError>;
14}
15
16impl<T, F> RenderProp<T> for F
18where
19 F: Fn(T) -> Result<Vec<Node>, ComponentError>,
20{
21 fn render(&self, data: T) -> Result<Vec<Node>, ComponentError> {
22 self(data)
23 }
24}
25
26pub struct RenderPropComponent<T, R>
28where
29 R: RenderProp<T>,
30{
31 component_id: ComponentId,
32 data: T,
33 renderer: R,
34 #[allow(dead_code)]
35 context: Context,
36}
37
38impl<T, R> RenderPropComponent<T, R>
39where
40 T: Clone + Send + Sync + 'static,
41 R: RenderProp<T> + Send + Sync + 'static,
42{
43 pub fn new(data: T, renderer: R, context: Context) -> Self {
44 Self {
45 component_id: ComponentId::new(),
46 data,
47 renderer,
48 context,
49 }
50 }
51}
52
53#[derive(Clone)]
55pub struct RenderPropProps<T, R>
56where
57 T: Clone,
58 R: Clone,
59{
60 pub data: T,
61 pub renderer: R,
62}
63
64impl<T, R> Component for RenderPropComponent<T, R>
67where
68 T: Clone + Send + Sync + 'static,
69 R: RenderProp<T> + Clone + Send + Sync + 'static,
70{
71 type Props = RenderPropProps<T, R>;
72
73 fn component_id(&self) -> ComponentId {
74 self.component_id
75 }
76
77 fn create(props: Self::Props, context: Context) -> Self {
78 Self::new(props.data, props.renderer, context)
79 }
80
81 fn update(&mut self, props: Self::Props) -> Result<(), ComponentError> {
82 self.data = props.data;
83 self.renderer = props.renderer;
84 Ok(())
85 }
86
87 fn render(&self) -> Result<Vec<Node>, ComponentError> {
88 self.renderer.render(self.data.clone())
89 }
90
91 fn as_any(&self) -> &dyn std::any::Any {
92 self
93 }
94
95 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
96 self
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct Slot {
103 pub name: String,
104 pub nodes: Vec<Node>,
105 pub required: bool,
106}
107
108impl Slot {
109 pub fn new(name: impl Into<String>) -> Self {
110 Self {
111 name: name.into(),
112 nodes: Vec::new(),
113 required: false,
114 }
115 }
116
117 pub fn required(mut self) -> Self {
118 self.required = true;
119 self
120 }
121
122 pub fn with_nodes(mut self, nodes: Vec<Node>) -> Self {
123 self.nodes = nodes;
124 self
125 }
126}
127
128#[derive(Clone)]
130pub struct SlottedProps {
131 pub slots: HashMap<String, Slot>,
132}
133
134impl SlottedProps {
135 pub fn new() -> Self {
136 Self {
137 slots: HashMap::new(),
138 }
139 }
140
141 pub fn with_slot(mut self, slot: Slot) -> Self {
142 self.slots.insert(slot.name.clone(), slot);
143 self
144 }
145
146 pub fn get_slot(&self, name: &str) -> Option<&Slot> {
147 self.slots.get(name)
148 }
149 pub fn get_slot_nodes(&self, name: &str) -> Vec<Node> {
150 self.slots
151 .get(name)
152 .map(|slot| slot.nodes.clone())
153 .unwrap_or_default()
154 }
155}
156
157impl Default for SlottedProps {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163pub trait Slotted {
165 fn supported_slots(&self) -> Vec<String>;
167
168 fn validate_slots(&self, props: &SlottedProps) -> Result<(), ComponentError> {
170 for slot_name in self.supported_slots() {
171 if let Some(slot) = props.get_slot(&slot_name) {
172 if slot.required && slot.nodes.is_empty() {
173 return Err(ComponentError::InvalidProps(format!(
174 "Required slot '{slot_name}' is empty"
175 )));
176 }
177 }
178 }
179 Ok(())
180 }
181
182 fn render_with_slots(&self, props: &SlottedProps) -> Result<Vec<Node>, ComponentError>;
184}
185
186pub struct SlottedComponent {
188 component_id: ComponentId,
189 supported_slots: Vec<String>,
190 #[allow(dead_code)]
191 context: Context,
192}
193
194impl SlottedComponent {
195 pub fn new(supported_slots: Vec<String>, context: Context) -> Self {
196 Self {
197 component_id: ComponentId::new(),
198 supported_slots,
199 context,
200 }
201 }
202}
203
204impl Component for SlottedComponent {
205 type Props = SlottedProps;
206
207 fn component_id(&self) -> ComponentId {
208 self.component_id
209 }
210
211 fn create(props: Self::Props, context: Context) -> Self {
212 let supported_slots = props.slots.keys().cloned().collect();
214 Self::new(supported_slots, context)
215 }
216
217 fn update(&mut self, _props: Self::Props) -> Result<(), ComponentError> {
218 Ok(())
219 }
220
221 fn render(&self) -> Result<Vec<Node>, ComponentError> {
222 Ok(Vec::new())
224 }
225
226 fn as_any(&self) -> &dyn std::any::Any {
227 self
228 }
229
230 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
231 self
232 }
233}
234
235impl Slotted for SlottedComponent {
236 fn supported_slots(&self) -> Vec<String> {
237 self.supported_slots.clone()
238 }
239
240 fn render_with_slots(&self, props: &SlottedProps) -> Result<Vec<Node>, ComponentError> {
241 self.validate_slots(props)?;
242
243 let mut nodes = Vec::new();
244 for slot_name in &self.supported_slots {
245 nodes.extend(props.get_slot_nodes(slot_name));
246 }
247 Ok(nodes)
248 }
249}
250
251pub trait CompoundComponent {
253 type SubComponents;
254
255 fn sub_components(&self) -> &Self::SubComponents;
257
258 fn sub_components_mut(&mut self) -> &mut Self::SubComponents;
260
261 fn render_compound(&self) -> Result<Vec<Node>, ComponentError>;
263}
264
265pub struct FlexibleCompoundComponent {
267 component_id: ComponentId,
268 sub_components: Vec<Box<dyn Component<Props = FlexibleCompoundProps>>>,
269 #[allow(dead_code)]
270 context: Context,
271}
272
273#[derive(Clone)]
274pub struct FlexibleCompoundProps {
275 pub children: Vec<FlexibleCompoundProps>,
276}
277
278impl FlexibleCompoundComponent {
279 pub fn new(context: Context) -> Self {
280 Self {
281 component_id: ComponentId::new(),
282 sub_components: Vec::new(),
283 context,
284 }
285 }
286
287 pub fn add_sub_component(
288 &mut self,
289 component: Box<dyn Component<Props = FlexibleCompoundProps>>,
290 ) {
291 self.sub_components.push(component);
292 }
293}
294
295impl Component for FlexibleCompoundComponent {
296 type Props = FlexibleCompoundProps;
297
298 fn component_id(&self) -> ComponentId {
299 self.component_id
300 }
301 fn create(_props: Self::Props, context: Context) -> Self {
302 Self::new(context)
306 }
307
308 fn update(&mut self, props: Self::Props) -> Result<(), ComponentError> {
309 for (i, child_props) in props.children.iter().enumerate() {
311 if let Some(sub_component) = self.sub_components.get_mut(i) {
312 sub_component.update(child_props.clone())?;
313 }
314 }
315 Ok(())
316 }
317
318 fn render(&self) -> Result<Vec<Node>, ComponentError> {
319 let mut all_nodes = Vec::new();
320 for sub_component in &self.sub_components {
321 all_nodes.extend(sub_component.render()?);
322 }
323 Ok(all_nodes)
324 }
325
326 fn as_any(&self) -> &dyn std::any::Any {
327 self
328 }
329
330 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
331 self
332 }
333}
334
335impl CompoundComponent for FlexibleCompoundComponent {
336 type SubComponents = Vec<Box<dyn Component<Props = FlexibleCompoundProps>>>;
337
338 fn sub_components(&self) -> &Self::SubComponents {
339 &self.sub_components
340 }
341
342 fn sub_components_mut(&mut self) -> &mut Self::SubComponents {
343 &mut self.sub_components
344 }
345
346 fn render_compound(&self) -> Result<Vec<Node>, ComponentError> {
347 self.render()
348 }
349}
350
351pub struct CompositionBuilder {
355 components: Vec<Box<dyn Component<Props = FlexibleCompoundProps>>>,
356 context: Context,
357}
358
359impl CompositionBuilder {
360 pub fn new(context: Context) -> Self {
361 Self {
362 components: Vec::new(),
363 context,
364 }
365 }
366
367 pub fn add_component(
368 mut self,
369 component: Box<dyn Component<Props = FlexibleCompoundProps>>,
370 ) -> Self {
371 self.components.push(component);
372 self
373 }
374
375 pub fn build(self) -> FlexibleCompoundComponent {
376 let mut compound = FlexibleCompoundComponent::new(self.context);
377 for component in self.components {
378 compound.add_sub_component(component);
379 }
380 compound
381 }
382}
383
384#[macro_export]
388macro_rules! slot {
389 ($name:expr) => {
390 Slot::new($name)
391 };
392 ($name:expr, required) => {
393 Slot::new($name).required()
394 };
395 ($name:expr, $($node:expr),*) => {
396 Slot::new($name).with_nodes(vec![$($node),*])
397 };
398}
399
400#[macro_export]
402macro_rules! slotted_props {
403 ($($slot:expr),*) => {
404 {
405 let mut props = SlottedProps::new();
406 $(
407 props = props.with_slot($slot);
408 )*
409 props
410 }
411 };
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::component::ComponentBase;
418
419 struct TestComponent {
420 base: ComponentBase,
421 }
422
423 #[derive(Clone)]
424 struct TestProps;
425 impl Component for TestComponent {
428 type Props = TestProps;
429
430 fn component_id(&self) -> ComponentId {
431 self.base.id()
432 }
433
434 fn create(_props: Self::Props, context: Context) -> Self {
435 Self {
436 base: ComponentBase::new(context),
437 }
438 }
439
440 fn update(&mut self, _props: Self::Props) -> Result<(), ComponentError> {
441 Ok(())
442 }
443
444 fn render(&self) -> Result<Vec<Node>, ComponentError> {
445 Ok(vec![])
446 }
447
448 fn as_any(&self) -> &dyn std::any::Any {
449 self
450 }
451
452 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
453 self
454 }
455 }
456
457 #[test]
458 fn test_slot_creation() {
459 let slot = slot!("header");
460 assert_eq!(slot.name, "header");
461 assert!(!slot.required);
462
463 let required_slot = slot!("content", required);
464 assert!(required_slot.required);
465 }
466
467 #[test]
468 fn test_slotted_props() {
469 let props = slotted_props!(slot!("header"), slot!("content", required));
470
471 assert!(props.get_slot("header").is_some());
472 assert!(props.get_slot("content").is_some());
473 assert!(props.get_slot("footer").is_none());
474 }
475
476 #[test]
477 fn test_slotted_component() {
478 let context = Context::new();
479 let slots = vec!["header".to_string(), "content".to_string()];
480 let component = SlottedComponent::new(slots, context);
481
482 assert_eq!(component.supported_slots(), vec!["header", "content"]);
483 }
484
485 #[test]
486 fn test_composition_builder() {
487 let context = Context::new();
488 let builder = CompositionBuilder::new(context);
489 let compound = builder.build();
490
491 assert_eq!(compound.sub_components().len(), 0);
492 }
493}