orbit/component/
hoc.rs

1//! Higher-order components (HOCs) for Orbit UI framework
2//!
3//! This module provides utilities for creating higher-order components that wrap
4//! existing components with additional functionality.
5
6use std::marker::PhantomData;
7
8use crate::component::{
9    Component, ComponentError, ComponentId, Context, LifecyclePhase, MountContext, Node, Props,
10    StateChanges, UnmountContext,
11};
12
13/// Trait for defining higher-order component behavior
14pub trait HigherOrderComponent<T: Component> {
15    /// The props type for the HOC itself (should match T::Props)
16    type HOCProps: Props + Clone;
17    /// Transform HOC props into wrapped component props
18    fn transform_props(hoc_props: &Self::HOCProps) -> T::Props;
19
20    /// Optional: modify the wrapped component after creation
21    fn enhance_component(
22        component: &mut T,
23        hoc_props: &Self::HOCProps,
24    ) -> Result<(), ComponentError> {
25        // Default implementation does nothing
26        let _ = (component, hoc_props);
27        Ok(())
28    }
29
30    /// Optional: intercept lifecycle events
31    fn on_wrapped_mount(
32        component: &mut T,
33        context: &MountContext,
34        hoc_props: &Self::HOCProps,
35    ) -> Result<(), ComponentError> {
36        let _ = (component, context, hoc_props);
37        Ok(())
38    }
39
40    /// Optional: intercept state changes
41    fn on_wrapped_update(
42        component: &mut T,
43        changes: &StateChanges,
44        hoc_props: &Self::HOCProps,
45    ) -> Result<(), ComponentError> {
46        let _ = (component, changes, hoc_props);
47        Ok(())
48    }
49
50    /// Optional: intercept unmount
51    fn on_wrapped_unmount(
52        component: &mut T,
53        context: &UnmountContext,
54        hoc_props: &Self::HOCProps,
55    ) -> Result<(), ComponentError> {
56        let _ = (component, context, hoc_props);
57        Ok(())
58    }
59}
60
61/// A generic higher-order component wrapper
62pub struct HOCWrapper<H, T>
63where
64    H: HigherOrderComponent<T>,
65    T: Component,
66{
67    /// The wrapped component instance
68    wrapped_component: T,
69    /// HOC-specific props
70    hoc_props: H::HOCProps,
71    /// Component base functionality
72    component_id: ComponentId,
73    /// Current lifecycle phase
74    lifecycle_phase: LifecyclePhase,
75    /// Phantom data for type safety
76    _phantom: PhantomData<H>,
77}
78
79impl<H, T> HOCWrapper<H, T>
80where
81    H: HigherOrderComponent<T>,
82    T: Component,
83{
84    /// Create a new HOC wrapper
85    pub fn new(hoc_props: H::HOCProps, context: Context) -> Result<Self, ComponentError>
86    where
87        T: Component,
88    {
89        let wrapped_props = H::transform_props(&hoc_props);
90        let mut wrapped_component = T::create(wrapped_props, context);
91
92        // Allow HOC to enhance the component
93        H::enhance_component(&mut wrapped_component, &hoc_props)?;
94
95        Ok(Self {
96            wrapped_component,
97            hoc_props,
98            component_id: ComponentId::new(),
99            lifecycle_phase: LifecyclePhase::Created,
100            _phantom: PhantomData,
101        })
102    }
103
104    /// Get a reference to the wrapped component
105    pub fn wrapped_component(&self) -> &T {
106        &self.wrapped_component
107    }
108
109    /// Get a mutable reference to the wrapped component
110    pub fn wrapped_component_mut(&mut self) -> &mut T {
111        &mut self.wrapped_component
112    }
113
114    /// Get the HOC props
115    pub fn hoc_props(&self) -> &H::HOCProps {
116        &self.hoc_props
117    }
118}
119
120impl<H, T> Component for HOCWrapper<H, T>
121where
122    H: HigherOrderComponent<T> + Send + Sync + 'static,
123    T: Component + Send + Sync + 'static,
124    H::HOCProps: Send + Sync + 'static,
125{
126    type Props = H::HOCProps;
127
128    fn component_id(&self) -> ComponentId {
129        self.component_id
130    }
131
132    fn create(props: Self::Props, context: Context) -> Self
133    where
134        Self: Sized,
135    {
136        Self::new(props, context).expect("Failed to create HOC wrapper")
137    }
138
139    fn initialize(&mut self) -> Result<(), ComponentError> {
140        self.wrapped_component.initialize()
141    }
142
143    fn before_mount(&mut self) -> Result<(), ComponentError> {
144        self.wrapped_component.before_mount()
145    }
146
147    fn on_mount(&mut self, context: &MountContext) -> Result<(), ComponentError> {
148        // Call HOC interceptor first
149        H::on_wrapped_mount(&mut self.wrapped_component, context, &self.hoc_props)?;
150        // Then call wrapped component's mount
151        self.wrapped_component.on_mount(context)
152    }
153
154    fn after_mount(&mut self) -> Result<(), ComponentError> {
155        self.wrapped_component.after_mount()
156    }
157
158    fn on_update(&mut self, changes: &StateChanges) -> Result<(), ComponentError> {
159        // Call HOC interceptor first
160        H::on_wrapped_update(&mut self.wrapped_component, changes, &self.hoc_props)?;
161        // Then call wrapped component's update
162        self.wrapped_component.on_update(changes)
163    }
164    fn should_update(&self, new_props: &Self::Props) -> bool {
165        // Transform props and check if wrapped component should update
166        let wrapped_props = H::transform_props(new_props);
167        self.wrapped_component.should_update(&wrapped_props)
168    }
169
170    fn before_update(&mut self, new_props: &Self::Props) -> Result<(), ComponentError> {
171        let wrapped_props = H::transform_props(new_props);
172        self.wrapped_component.before_update(&wrapped_props)
173    }
174
175    fn update(&mut self, props: Self::Props) -> Result<(), ComponentError> {
176        let wrapped_props = H::transform_props(&props);
177        self.hoc_props = props;
178        self.wrapped_component.update(wrapped_props)
179    }
180
181    fn after_update(&mut self) -> Result<(), ComponentError> {
182        self.wrapped_component.after_update()
183    }
184
185    fn before_unmount(&mut self) -> Result<(), ComponentError> {
186        self.wrapped_component.before_unmount()
187    }
188
189    fn on_unmount(&mut self, context: &UnmountContext) -> Result<(), ComponentError> {
190        // Call HOC interceptor first
191        H::on_wrapped_unmount(&mut self.wrapped_component, context, &self.hoc_props)?;
192        // Then call wrapped component's unmount
193        self.wrapped_component.on_unmount(context)
194    }
195
196    fn after_unmount(&mut self) -> Result<(), ComponentError> {
197        self.wrapped_component.after_unmount()
198    }
199
200    fn render(&self) -> Result<Vec<Node>, ComponentError> {
201        self.wrapped_component.render()
202    }
203    fn lifecycle_phase(&self) -> LifecyclePhase {
204        self.lifecycle_phase
205    }
206
207    fn mount(&mut self) -> Result<(), ComponentError> {
208        self.wrapped_component.mount()
209    }
210
211    fn unmount(&mut self) -> Result<(), ComponentError> {
212        self.wrapped_component.unmount()
213    }
214
215    fn cleanup(&mut self) -> Result<(), ComponentError> {
216        self.wrapped_component.cleanup()
217    }
218
219    fn state_changed(&mut self, state_key: &str) -> Result<(), ComponentError> {
220        self.wrapped_component.state_changed(state_key)
221    }
222    fn request_update(&mut self) -> Result<(), ComponentError> {
223        <T as Component>::request_update(&mut self.wrapped_component)
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
235/// Common HOC patterns
236///
237/// HOC that adds logging to component lifecycle events
238pub struct WithLogging;
239
240impl<T: Component> HigherOrderComponent<T> for WithLogging {
241    type HOCProps = T::Props;
242
243    fn transform_props(hoc_props: &Self::HOCProps) -> T::Props {
244        hoc_props.clone()
245    }
246    fn on_wrapped_mount(
247        component: &mut T,
248        _context: &MountContext,
249        _hoc_props: &Self::HOCProps,
250    ) -> Result<(), ComponentError> {
251        println!(
252            "🔄 Component {} mounted",
253            Component::component_id(component)
254        );
255        Ok(())
256    }
257
258    fn on_wrapped_update(
259        component: &mut T,
260        changes: &StateChanges,
261        _hoc_props: &Self::HOCProps,
262    ) -> Result<(), ComponentError> {
263        println!(
264            "🔄 Component {} updated with {} changes",
265            Component::component_id(component),
266            changes.changes.len()
267        );
268        Ok(())
269    }
270    fn on_wrapped_unmount(
271        component: &mut T,
272        _context: &UnmountContext,
273        _hoc_props: &Self::HOCProps,
274    ) -> Result<(), ComponentError> {
275        println!(
276            "🔄 Component {} unmounted",
277            Component::component_id(component)
278        );
279        Ok(())
280    }
281}
282
283/// HOC that adds performance monitoring
284pub struct WithPerformanceMonitoring;
285
286impl<T: Component> HigherOrderComponent<T> for WithPerformanceMonitoring {
287    type HOCProps = T::Props;
288
289    fn transform_props(hoc_props: &Self::HOCProps) -> T::Props {
290        hoc_props.clone()
291    }
292    fn on_wrapped_mount(
293        component: &mut T,
294        _context: &MountContext,
295        _hoc_props: &Self::HOCProps,
296    ) -> Result<(), ComponentError> {
297        let start = std::time::Instant::now();
298        let result = component.mount();
299        let duration = start.elapsed();
300        println!(
301            "âš¡ Component {} mount took {:?}",
302            Component::component_id(component),
303            duration
304        );
305        result
306    }
307
308    fn on_wrapped_update(
309        component: &mut T,
310        changes: &StateChanges,
311        _hoc_props: &Self::HOCProps,
312    ) -> Result<(), ComponentError> {
313        let start = std::time::Instant::now();
314        let result = component.on_update(changes);
315        let duration = start.elapsed();
316        println!(
317            "âš¡ Component {} update took {:?}",
318            Component::component_id(component),
319            duration
320        );
321        result
322    }
323}
324
325/// Convenient type aliases for common HOCs
326pub type LoggedComponent<T> = HOCWrapper<WithLogging, T>;
327pub type MonitoredComponent<T> = HOCWrapper<WithPerformanceMonitoring, T>;
328
329/// Macro for easy HOC creation
330#[macro_export]
331macro_rules! with_hoc {
332    ($hoc:ty, $component:ty, $props:expr, $context:expr) => {
333        HOCWrapper::<$hoc, $component>::new($props, $context)
334    };
335}
336
337/// Macro for chaining multiple HOCs
338#[macro_export]
339macro_rules! hoc_chain {
340    ($component:ty, $props:expr, $context:expr, $($hoc:ty),+) => {{
341        let mut component = $component::create($props, $context);
342        $(
343            component = HOCWrapper::<$hoc, _>::new(component.props().clone(), $context)?;
344        )+
345        component
346    }};
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use crate::component::{ComponentBase, Context};
353
354    #[derive(Clone, Debug)]
355    struct TestProps {
356        name: String,
357    }
358    // Using the blanket implementation of Props instead of implementing it manually
359
360    struct TestComponent {
361        base: ComponentBase,
362        props: TestProps,
363    }
364
365    impl Component for TestComponent {
366        type Props = TestProps;
367
368        fn component_id(&self) -> ComponentId {
369            self.base.id()
370        }
371
372        fn create(props: Self::Props, context: Context) -> Self {
373            Self {
374                base: ComponentBase::new(context),
375                props,
376            }
377        }
378
379        fn update(&mut self, props: Self::Props) -> Result<(), ComponentError> {
380            self.props = props;
381            Ok(())
382        }
383
384        fn render(&self) -> Result<Vec<Node>, ComponentError> {
385            Ok(vec![])
386        }
387
388        fn as_any(&self) -> &dyn std::any::Any {
389            self
390        }
391
392        fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
393            self
394        }
395    }
396
397    #[test]
398    fn test_logging_hoc() {
399        let context = Context::new();
400        let props = TestProps {
401            name: "test".to_string(),
402        };
403
404        let mut logged_component = LoggedComponent::<TestComponent>::new(props, context).unwrap();
405
406        // Test that we can access the wrapped component
407        assert_eq!(logged_component.wrapped_component().props.name, "test");
408
409        // Test lifecycle methods work
410        assert!(logged_component.initialize().is_ok());
411        assert!(logged_component.before_mount().is_ok());
412    }
413
414    #[test]
415    fn test_performance_monitoring_hoc() {
416        let context = Context::new();
417        let props = TestProps {
418            name: "test".to_string(),
419        };
420
421        let mut monitored_component =
422            MonitoredComponent::<TestComponent>::new(props, context).unwrap();
423
424        // Test that monitoring doesn't break functionality
425        assert!(monitored_component.initialize().is_ok());
426    }
427}