9
9
10
10
# TODO: Move all this logic to `reactpy.utils._mutate_vdom()` and remove this file.
11
11
12
- UNSUPPORTED_PROPS = {"children" , "ref" , "aria-*" , "data-*" }
13
-
14
12
15
13
def convert_html_props_to_reactjs (vdom_tree : VdomDict ) -> VdomDict :
16
14
"""Transformation that standardizes the prop names to be used in the component."""
17
-
18
15
if not isinstance (vdom_tree , dict ):
19
16
return vdom_tree
20
17
@@ -38,69 +35,113 @@ def convert_textarea_children_to_prop(vdom_tree: VdomDict) -> VdomDict:
38
35
text_content = vdom_tree .pop ("children" )
39
36
text_content = "" .join ([child for child in text_content if isinstance (child , str )])
40
37
default_value = vdom_tree ["attributes" ].pop ("defaultValue" , "" )
41
- vdom_tree ["attributes" ]["value " ] = text_content or default_value
38
+ vdom_tree ["attributes" ]["defaultValue " ] = text_content or default_value
42
39
43
40
for child in vdom_tree .get ("children" , []):
44
41
convert_textarea_children_to_prop (child )
45
42
46
43
return vdom_tree
47
44
48
45
49
- def _find_selected_options (vdom_tree : VdomDict , mutation : Callable ) -> list [VdomDict ]:
50
- """Recursively iterate through the tree of dictionaries to find an <option> with the 'selected' prop."""
51
- selected_options = []
52
-
46
+ def set_value_prop_on_select_element (vdom_tree : VdomDict ) -> VdomDict :
47
+ """Use the `value` prop on <select> instead of setting `selected` on <option>."""
53
48
if not isinstance (vdom_tree , dict ):
54
- return selected_options
49
+ return vdom_tree
55
50
56
- if vdom_tree ["tagName" ] == "option" and "attributes" in vdom_tree and "selected" in vdom_tree ["attributes" ]:
57
- mutation (vdom_tree )
58
- selected_options .append (vdom_tree )
51
+ # If the current tag is <select>, remove 'selected' prop from any <option> children and
52
+ # instead set the 'value' prop on the <select> tag.
53
+ if vdom_tree ["tagName" ] == "select" and "children" in vdom_tree :
54
+ selected_options = _find_selected_options (vdom_tree )
55
+ multiple_choice = vdom_tree ["attributes" ]["multiple" ] = bool (vdom_tree ["attributes" ].get ("multiple" ))
56
+ if selected_options and not multiple_choice :
57
+ vdom_tree ["attributes" ]["defaultValue" ] = selected_options [0 ]
58
+ if selected_options and multiple_choice :
59
+ vdom_tree ["attributes" ]["defaultValue" ] = selected_options
59
60
60
61
for child in vdom_tree .get ("children" , []):
61
- selected_options . extend ( _find_selected_options ( child , mutation ) )
62
+ set_value_prop_on_select_element ( child )
62
63
63
- return selected_options
64
+ return vdom_tree
64
65
65
66
66
- def set_value_prop_on_select_element (vdom_tree : VdomDict ) -> VdomDict :
67
- """Use the `value` prop on <select> instead of setting `selected` on <option>."""
67
+ def ensure_input_elements_are_controlled (event_func : Callable | None = None ) -> Callable :
68
+ """Adds an onChange handler on form <input> elements, since ReactJS doesn't like uncontrolled inputs."""
69
+
70
+ def mutation (vdom_tree : VdomDict ) -> VdomDict :
71
+ """Adds an onChange event handler to all input elements."""
72
+ if not isinstance (vdom_tree , dict ):
73
+ return vdom_tree
74
+
75
+ vdom_tree .setdefault ("eventHandlers" , {})
76
+ if vdom_tree ["tagName" ] in {"input" , "textarea" }:
77
+ if "onChange" in vdom_tree ["eventHandlers" ]:
78
+ pass
79
+ elif isinstance (event_func , EventHandler ):
80
+ vdom_tree ["eventHandlers" ]["onChange" ] = event_func
81
+ else :
82
+ vdom_tree ["eventHandlers" ]["onChange" ] = EventHandler (
83
+ to_event_handler_function (event_func or _do_nothing_event )
84
+ )
85
+
86
+ if "children" in vdom_tree :
87
+ for child in vdom_tree ["children" ]:
88
+ mutation (child )
89
+
90
+ return vdom_tree
91
+
92
+ return mutation
93
+
68
94
95
+ def intercept_anchor_links (vdom_tree : VdomDict ) -> VdomDict :
96
+ """Intercepts anchor links and prevents the default behavior.
97
+ This allows ReactPy-Router to handle the navigation instead of the browser."""
69
98
if not isinstance (vdom_tree , dict ):
70
99
return vdom_tree
71
100
72
- # If the current tag is <select>, remove 'selected' prop from any <option> children and
73
- # instead set the 'value' prop on the <select> tag.
74
- # TODO: Fix this, is broken
75
- if vdom_tree ["tagName" ] == "select" and "children" in vdom_tree :
101
+ if vdom_tree ["tagName" ] == "a" :
76
102
vdom_tree .setdefault ("eventHandlers" , {})
77
- vdom_tree ["eventHandlers" ]["onChange" ] = EventHandler (to_event_handler_function (do_nothing_event ))
78
- selected_options = _find_selected_options (vdom_tree , lambda option : option ["attributes" ].pop ("selected" ))
79
- multiple_choice = vdom_tree ["attributes" ].get ("multiple" )
80
- if selected_options and not multiple_choice :
81
- vdom_tree ["attributes" ]["value" ] = selected_options [0 ]["children" ][0 ]
82
- if selected_options and multiple_choice :
83
- vdom_tree ["attributes" ]["value" ] = [option ["children" ][0 ] for option in selected_options ]
103
+ vdom_tree ["eventHandlers" ]["onClick" ] = EventHandler (
104
+ to_event_handler_function (_do_nothing_event ), prevent_default = True
105
+ )
84
106
85
107
for child in vdom_tree .get ("children" , []):
86
- set_value_prop_on_select_element (child )
108
+ intercept_anchor_links (child )
87
109
88
110
return vdom_tree
89
111
90
112
113
+ def _find_selected_options (vdom_tree : VdomDict ) -> list [str ]:
114
+ """Recursively iterate through the tree of dictionaries to find an <option> with the 'selected' prop."""
115
+ if not isinstance (vdom_tree , dict ):
116
+ return []
117
+
118
+ selected_options = []
119
+ if vdom_tree ["tagName" ] == "option" and "attributes" in vdom_tree :
120
+ value = vdom_tree ["attributes" ].setdefault ("value" , vdom_tree ["children" ][0 ])
121
+
122
+ if "selected" in vdom_tree ["attributes" ]:
123
+ vdom_tree ["attributes" ].pop ("selected" )
124
+ selected_options .append (value )
125
+
126
+ for child in vdom_tree .get ("children" , []):
127
+ selected_options .extend (_find_selected_options (child ))
128
+
129
+ return selected_options
130
+
131
+
91
132
def _normalize_prop_name (prop_name : str ) -> str :
92
133
"""Standardizes the prop name to be used in the component."""
93
134
return REACT_PROP_SUBSTITUTIONS .get (prop_name , prop_name )
94
135
95
136
96
- def react_props_set (string : str ) -> set [str ]:
137
+ def _react_props_set (string : str ) -> set [str ]:
97
138
"""Extracts the props from a string of React props."""
98
139
lines = string .strip ().split ("\n " )
99
140
props = set ()
100
141
101
142
for line in lines :
102
143
parts = line .split (":" , maxsplit = 1 )
103
- if len (parts ) == 2 and parts [0 ] not in UNSUPPORTED_PROPS :
144
+ if len (parts ) == 2 and parts [0 ] not in { "children" , "ref" , "aria-*" , "data-*" } :
104
145
key , value = parts
105
146
key = key .strip ()
106
147
value = value .strip ()
@@ -130,35 +171,7 @@ def _add_on_change_event(event_func, vdom_tree: VdomDict) -> VdomDict:
130
171
return vdom_tree
131
172
132
173
133
- def ensure_input_elements_are_controlled (event_func : Callable | None = None ) -> Callable :
134
- """Adds an onChange handler on form <input> elements, since ReactJS doesn't like uncontrolled inputs."""
135
-
136
- def mutation (vdom_tree : VdomDict ) -> VdomDict :
137
- """Adds an onChange event handler to all input elements."""
138
- if not isinstance (vdom_tree , dict ):
139
- return vdom_tree
140
-
141
- vdom_tree .setdefault ("eventHandlers" , {})
142
- if vdom_tree ["tagName" ] in {"input" , "textarea" }:
143
- if "onChange" in vdom_tree ["eventHandlers" ]:
144
- pass
145
- elif isinstance (event_func , EventHandler ):
146
- vdom_tree ["eventHandlers" ]["onChange" ] = event_func
147
- else :
148
- vdom_tree ["eventHandlers" ]["onChange" ] = EventHandler (
149
- to_event_handler_function (event_func or do_nothing_event )
150
- )
151
-
152
- if "children" in vdom_tree :
153
- for child in vdom_tree ["children" ]:
154
- mutation (child )
155
-
156
- return vdom_tree
157
-
158
- return mutation
159
-
160
-
161
- def do_nothing_event (* args , ** kwargs ):
174
+ def _do_nothing_event (* args , ** kwargs ):
162
175
pass
163
176
164
177
@@ -495,7 +508,7 @@ def do_nothing_event(*args, **kwargs):
495
508
type: a string. Says whether the script is a classic script, ES module, or import map.
496
509
"""
497
510
498
- KNOWN_REACT_PROPS = react_props_set (
511
+ KNOWN_REACT_PROPS = _react_props_set (
499
512
SPECIAL_PROPS
500
513
+ STANDARD_PROPS
501
514
+ FORM_PROPS
0 commit comments