1use crate::platform::{ViewType, PropValue, PropsDiff};
15use crate::layout::LayoutStyle;
16use std::collections::HashMap;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ChildrenMode {
25 Container,
27 Leaf,
29 TextOnly,
31}
32
33#[derive(Debug, Clone)]
35pub struct ComponentDescriptor {
36 pub name: &'static str,
37 pub view_type: ViewType,
38 pub children_mode: ChildrenMode,
39 pub default_props: PropsDiff,
40 pub default_style: LayoutStyle,
41}
42
43pub struct ComponentRegistry {
49 components: HashMap<&'static str, ComponentDescriptor>,
50}
51
52impl ComponentRegistry {
53 pub fn new() -> Self {
55 let mut registry = Self {
56 components: HashMap::new(),
57 };
58 registry.register_builtins();
59 registry
60 }
61
62 pub fn get(&self, name: &str) -> Option<&ComponentDescriptor> {
64 self.components.get(name)
65 }
66
67 pub fn resolve_view_type(&self, name: &str) -> Option<ViewType> {
69 self.components.get(name).map(|d| d.view_type.clone())
70 }
71
72 pub fn component_names(&self) -> Vec<&'static str> {
74 let mut names: Vec<_> = self.components.keys().copied().collect();
75 names.sort();
76 names
77 }
78
79 pub fn count(&self) -> usize {
81 self.components.len()
82 }
83
84 fn register(&mut self, descriptor: ComponentDescriptor) {
85 self.components.insert(descriptor.name, descriptor);
86 }
87
88 fn register_builtins(&mut self) {
89 self.register(view_component());
91 self.register(text_component());
92 self.register(image_component());
93 self.register(scroll_view_component());
94 self.register(text_input_component());
95
96 self.register(flat_list_component());
98 self.register(section_list_component());
99
100 self.register(stack_navigator_component());
102 self.register(tab_navigator_component());
103 self.register(drawer_navigator_component());
104
105 self.register(switch_component());
107 self.register(slider_component());
108 self.register(picker_component());
109 self.register(date_picker_component());
110
111 self.register(button_component());
113 self.register(pressable_component());
114 self.register(touchable_opacity_component());
115 self.register(activity_indicator_component());
116
117 self.register(safe_area_view_component());
119 self.register(keyboard_avoiding_view_component());
120 self.register(modal_component());
121
122 self.register(video_component());
124 self.register(camera_component());
125 self.register(web_view_component());
126 }
127}
128
129impl Default for ComponentRegistry {
130 fn default() -> Self { Self::new() }
131}
132
133fn view_component() -> ComponentDescriptor {
138 ComponentDescriptor {
139 name: "View",
140 view_type: ViewType::Container,
141 children_mode: ChildrenMode::Container,
142 default_props: PropsDiff::new(),
143 default_style: LayoutStyle::default(),
144 }
145}
146
147fn text_component() -> ComponentDescriptor {
148 let mut props = PropsDiff::new();
149 props.set("numberOfLines", PropValue::I32(0)); props.set("selectable", PropValue::Bool(false));
151
152 ComponentDescriptor {
153 name: "Text",
154 view_type: ViewType::Text,
155 children_mode: ChildrenMode::TextOnly,
156 default_props: props,
157 default_style: LayoutStyle::default(),
158 }
159}
160
161fn image_component() -> ComponentDescriptor {
162 let mut props = PropsDiff::new();
163 props.set("resizeMode", PropValue::String("cover".into()));
164
165 ComponentDescriptor {
166 name: "Image",
167 view_type: ViewType::Image,
168 children_mode: ChildrenMode::Leaf,
169 default_props: props,
170 default_style: LayoutStyle::default(),
171 }
172}
173
174fn scroll_view_component() -> ComponentDescriptor {
175 let mut props = PropsDiff::new();
176 props.set("horizontal", PropValue::Bool(false));
177 props.set("showsScrollIndicator", PropValue::Bool(true));
178 props.set("bounces", PropValue::Bool(true));
179 props.set("pagingEnabled", PropValue::Bool(false));
180
181 ComponentDescriptor {
182 name: "ScrollView",
183 view_type: ViewType::ScrollView,
184 children_mode: ChildrenMode::Container,
185 default_props: props,
186 default_style: LayoutStyle::default(),
187 }
188}
189
190fn text_input_component() -> ComponentDescriptor {
191 let mut props = PropsDiff::new();
192 props.set("editable", PropValue::Bool(true));
193 props.set("multiline", PropValue::Bool(false));
194 props.set("secureTextEntry", PropValue::Bool(false));
195 props.set("autoCapitalize", PropValue::String("sentences".into()));
196 props.set("autoCorrect", PropValue::Bool(true));
197 props.set("placeholder", PropValue::String(String::new()));
198
199 ComponentDescriptor {
200 name: "TextInput",
201 view_type: ViewType::TextInput,
202 children_mode: ChildrenMode::Leaf,
203 default_props: props,
204 default_style: LayoutStyle::default(),
205 }
206}
207
208fn flat_list_component() -> ComponentDescriptor {
213 let mut props = PropsDiff::new();
214 props.set("horizontal", PropValue::Bool(false));
215 props.set("initialNumToRender", PropValue::I32(10));
216 props.set("windowSize", PropValue::I32(21)); props.set("removeClippedSubviews", PropValue::Bool(true));
218
219 ComponentDescriptor {
220 name: "FlatList",
221 view_type: ViewType::ScrollView, children_mode: ChildrenMode::Container,
223 default_props: props,
224 default_style: LayoutStyle::default(),
225 }
226}
227
228fn section_list_component() -> ComponentDescriptor {
229 let mut props = PropsDiff::new();
230 props.set("stickySectionHeaders", PropValue::Bool(true));
231 props.set("initialNumToRender", PropValue::I32(10));
232
233 ComponentDescriptor {
234 name: "SectionList",
235 view_type: ViewType::ScrollView,
236 children_mode: ChildrenMode::Container,
237 default_props: props,
238 default_style: LayoutStyle::default(),
239 }
240}
241
242fn stack_navigator_component() -> ComponentDescriptor {
247 let mut props = PropsDiff::new();
248 props.set("headerShown", PropValue::Bool(true));
249 props.set("gestureEnabled", PropValue::Bool(true));
250 props.set("animationEnabled", PropValue::Bool(true));
251
252 ComponentDescriptor {
253 name: "StackNavigator",
254 view_type: ViewType::Container,
255 children_mode: ChildrenMode::Container,
256 default_props: props,
257 default_style: LayoutStyle::default(),
258 }
259}
260
261fn tab_navigator_component() -> ComponentDescriptor {
262 let mut props = PropsDiff::new();
263 props.set("tabBarPosition", PropValue::String("bottom".into()));
264 props.set("lazy", PropValue::Bool(true));
265
266 ComponentDescriptor {
267 name: "TabNavigator",
268 view_type: ViewType::Container,
269 children_mode: ChildrenMode::Container,
270 default_props: props,
271 default_style: LayoutStyle::default(),
272 }
273}
274
275fn drawer_navigator_component() -> ComponentDescriptor {
276 let mut props = PropsDiff::new();
277 props.set("drawerPosition", PropValue::String("left".into()));
278 props.set("drawerType", PropValue::String("front".into()));
279 props.set("swipeEnabled", PropValue::Bool(true));
280
281 ComponentDescriptor {
282 name: "DrawerNavigator",
283 view_type: ViewType::Container,
284 children_mode: ChildrenMode::Container,
285 default_props: props,
286 default_style: LayoutStyle::default(),
287 }
288}
289
290fn switch_component() -> ComponentDescriptor {
295 let mut props = PropsDiff::new();
296 props.set("value", PropValue::Bool(false));
297 props.set("disabled", PropValue::Bool(false));
298
299 ComponentDescriptor {
300 name: "Switch",
301 view_type: ViewType::Switch,
302 children_mode: ChildrenMode::Leaf,
303 default_props: props,
304 default_style: LayoutStyle::default(),
305 }
306}
307
308fn slider_component() -> ComponentDescriptor {
309 let mut props = PropsDiff::new();
310 props.set("minimumValue", PropValue::F32(0.0));
311 props.set("maximumValue", PropValue::F32(1.0));
312 props.set("step", PropValue::F32(0.0)); props.set("value", PropValue::F32(0.0));
314 props.set("disabled", PropValue::Bool(false));
315
316 ComponentDescriptor {
317 name: "Slider",
318 view_type: ViewType::Slider,
319 children_mode: ChildrenMode::Leaf,
320 default_props: props,
321 default_style: LayoutStyle::default(),
322 }
323}
324
325fn picker_component() -> ComponentDescriptor {
326 let mut props = PropsDiff::new();
327 props.set("enabled", PropValue::Bool(true));
328
329 ComponentDescriptor {
330 name: "Picker",
331 view_type: ViewType::Custom("Picker".into()),
332 children_mode: ChildrenMode::Leaf,
333 default_props: props,
334 default_style: LayoutStyle::default(),
335 }
336}
337
338fn date_picker_component() -> ComponentDescriptor {
339 let mut props = PropsDiff::new();
340 props.set("mode", PropValue::String("date".into())); ComponentDescriptor {
343 name: "DatePicker",
344 view_type: ViewType::DatePicker,
345 children_mode: ChildrenMode::Leaf,
346 default_props: props,
347 default_style: LayoutStyle::default(),
348 }
349}
350
351fn button_component() -> ComponentDescriptor {
356 let mut props = PropsDiff::new();
357 props.set("disabled", PropValue::Bool(false));
358
359 ComponentDescriptor {
360 name: "Button",
361 view_type: ViewType::Button,
362 children_mode: ChildrenMode::TextOnly,
363 default_props: props,
364 default_style: LayoutStyle::default(),
365 }
366}
367
368fn pressable_component() -> ComponentDescriptor {
369 let mut props = PropsDiff::new();
370 props.set("disabled", PropValue::Bool(false));
371
372 ComponentDescriptor {
373 name: "Pressable",
374 view_type: ViewType::Container,
375 children_mode: ChildrenMode::Container,
376 default_props: props,
377 default_style: LayoutStyle::default(),
378 }
379}
380
381fn touchable_opacity_component() -> ComponentDescriptor {
382 let mut props = PropsDiff::new();
383 props.set("activeOpacity", PropValue::F32(0.2));
384 props.set("disabled", PropValue::Bool(false));
385
386 ComponentDescriptor {
387 name: "TouchableOpacity",
388 view_type: ViewType::Container,
389 children_mode: ChildrenMode::Container,
390 default_props: props,
391 default_style: LayoutStyle::default(),
392 }
393}
394
395fn activity_indicator_component() -> ComponentDescriptor {
396 let mut props = PropsDiff::new();
397 props.set("animating", PropValue::Bool(true));
398 props.set("size", PropValue::String("small".into()));
399
400 ComponentDescriptor {
401 name: "ActivityIndicator",
402 view_type: ViewType::ActivityIndicator,
403 children_mode: ChildrenMode::Leaf,
404 default_props: props,
405 default_style: LayoutStyle::default(),
406 }
407}
408
409fn safe_area_view_component() -> ComponentDescriptor {
414 ComponentDescriptor {
415 name: "SafeAreaView",
416 view_type: ViewType::Container,
417 children_mode: ChildrenMode::Container,
418 default_props: PropsDiff::new(),
419 default_style: LayoutStyle::default(),
420 }
421}
422
423fn keyboard_avoiding_view_component() -> ComponentDescriptor {
424 let mut props = PropsDiff::new();
425 props.set("behavior", PropValue::String("padding".into())); props.set("enabled", PropValue::Bool(true));
427
428 ComponentDescriptor {
429 name: "KeyboardAvoidingView",
430 view_type: ViewType::Container,
431 children_mode: ChildrenMode::Container,
432 default_props: props,
433 default_style: LayoutStyle::default(),
434 }
435}
436
437fn modal_component() -> ComponentDescriptor {
438 let mut props = PropsDiff::new();
439 props.set("visible", PropValue::Bool(false));
440 props.set("animationType", PropValue::String("none".into())); props.set("transparent", PropValue::Bool(false));
442
443 ComponentDescriptor {
444 name: "Modal",
445 view_type: ViewType::Modal,
446 children_mode: ChildrenMode::Container,
447 default_props: props,
448 default_style: LayoutStyle::default(),
449 }
450}
451
452fn video_component() -> ComponentDescriptor {
457 let mut props = PropsDiff::new();
458 props.set("paused", PropValue::Bool(true));
459 props.set("muted", PropValue::Bool(false));
460 props.set("repeat", PropValue::Bool(false));
461 props.set("resizeMode", PropValue::String("contain".into()));
462 props.set("controls", PropValue::Bool(true));
463
464 ComponentDescriptor {
465 name: "Video",
466 view_type: ViewType::Custom("Video".into()),
467 children_mode: ChildrenMode::Leaf,
468 default_props: props,
469 default_style: LayoutStyle::default(),
470 }
471}
472
473fn camera_component() -> ComponentDescriptor {
474 let mut props = PropsDiff::new();
475 props.set("facing", PropValue::String("back".into())); props.set("flashMode", PropValue::String("off".into())); ComponentDescriptor {
479 name: "Camera",
480 view_type: ViewType::Custom("Camera".into()),
481 children_mode: ChildrenMode::Leaf,
482 default_props: props,
483 default_style: LayoutStyle::default(),
484 }
485}
486
487fn web_view_component() -> ComponentDescriptor {
488 let mut props = PropsDiff::new();
489 props.set("javaScriptEnabled", PropValue::Bool(true));
490 props.set("domStorageEnabled", PropValue::Bool(true));
491 props.set("scalesPageToFit", PropValue::Bool(true));
492
493 ComponentDescriptor {
494 name: "WebView",
495 view_type: ViewType::Custom("WebView".into()),
496 children_mode: ChildrenMode::Leaf,
497 default_props: props,
498 default_style: LayoutStyle::default(),
499 }
500}
501
502#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn test_registry_has_all_components() {
512 let registry = ComponentRegistry::new();
513 assert_eq!(registry.count(), 24);
515 }
516
517 #[test]
518 fn test_core_primitives_registered() {
519 let registry = ComponentRegistry::new();
520 assert!(registry.get("View").is_some());
521 assert!(registry.get("Text").is_some());
522 assert!(registry.get("Image").is_some());
523 assert!(registry.get("ScrollView").is_some());
524 assert!(registry.get("TextInput").is_some());
525 }
526
527 #[test]
528 fn test_view_type_mapping() {
529 let registry = ComponentRegistry::new();
530 assert_eq!(registry.resolve_view_type("View"), Some(ViewType::Container));
531 assert_eq!(registry.resolve_view_type("Text"), Some(ViewType::Text));
532 assert_eq!(registry.resolve_view_type("Image"), Some(ViewType::Image));
533 assert_eq!(registry.resolve_view_type("Button"), Some(ViewType::Button));
534 assert_eq!(registry.resolve_view_type("Switch"), Some(ViewType::Switch));
535 assert_eq!(registry.resolve_view_type("Modal"), Some(ViewType::Modal));
536 }
537
538 #[test]
539 fn test_children_mode() {
540 let registry = ComponentRegistry::new();
541 assert_eq!(registry.get("View").unwrap().children_mode, ChildrenMode::Container);
542 assert_eq!(registry.get("Text").unwrap().children_mode, ChildrenMode::TextOnly);
543 assert_eq!(registry.get("Image").unwrap().children_mode, ChildrenMode::Leaf);
544 assert_eq!(registry.get("ActivityIndicator").unwrap().children_mode, ChildrenMode::Leaf);
545 assert_eq!(registry.get("ScrollView").unwrap().children_mode, ChildrenMode::Container);
546 }
547
548 #[test]
549 fn test_default_props() {
550 let registry = ComponentRegistry::new();
551
552 let text_input = registry.get("TextInput").unwrap();
553 assert!(!text_input.default_props.is_empty());
554
555 let view = registry.get("View").unwrap();
556 assert!(view.default_props.is_empty());
557 }
558
559 #[test]
560 fn test_component_names_sorted() {
561 let registry = ComponentRegistry::new();
562 let names = registry.component_names();
563 assert!(names.len() == 24);
564 let mut sorted = names.clone();
566 sorted.sort();
567 assert_eq!(names, sorted);
568 }
569
570 #[test]
571 fn test_unknown_component() {
572 let registry = ComponentRegistry::new();
573 assert!(registry.get("NonExistent").is_none());
574 assert_eq!(registry.resolve_view_type("NonExistent"), None);
575 }
576
577 #[test]
578 fn test_list_components() {
579 let registry = ComponentRegistry::new();
580 let flat_list = registry.get("FlatList").unwrap();
581 assert_eq!(flat_list.view_type, ViewType::ScrollView);
582 assert_eq!(flat_list.children_mode, ChildrenMode::Container);
583
584 let section_list = registry.get("SectionList").unwrap();
585 assert_eq!(section_list.view_type, ViewType::ScrollView);
586 }
587
588 #[test]
589 fn test_navigation_components() {
590 let registry = ComponentRegistry::new();
591 assert!(registry.get("StackNavigator").is_some());
592 assert!(registry.get("TabNavigator").is_some());
593 assert!(registry.get("DrawerNavigator").is_some());
594 }
595
596 #[test]
597 fn test_media_components() {
598 let registry = ComponentRegistry::new();
599 let video = registry.get("Video").unwrap();
600 assert_eq!(video.view_type, ViewType::Custom("Video".into()));
601 assert_eq!(video.children_mode, ChildrenMode::Leaf);
602
603 let camera = registry.get("Camera").unwrap();
604 assert_eq!(camera.view_type, ViewType::Custom("Camera".into()));
605
606 let webview = registry.get("WebView").unwrap();
607 assert_eq!(webview.view_type, ViewType::Custom("WebView".into()));
608 }
609}