99
1010# TODO: Move all this logic to `reactpy.utils._mutate_vdom()` and remove this file.
1111
12- UNSUPPORTED_PROPS = {"children" , "ref" , "aria-*" , "data-*" }
13-
1412
1513def convert_html_props_to_reactjs (vdom_tree : VdomDict ) -> VdomDict :
1614 """Transformation that standardizes the prop names to be used in the component."""
17-
1815 if not isinstance (vdom_tree , dict ):
1916 return vdom_tree
2017
@@ -38,69 +35,113 @@ def convert_textarea_children_to_prop(vdom_tree: VdomDict) -> VdomDict:
3835 text_content = vdom_tree .pop ("children" )
3936 text_content = "" .join ([child for child in text_content if isinstance (child , str )])
4037 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
4239
4340 for child in vdom_tree .get ("children" , []):
4441 convert_textarea_children_to_prop (child )
4542
4643 return vdom_tree
4744
4845
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>."""
5348 if not isinstance (vdom_tree , dict ):
54- return selected_options
49+ return vdom_tree
5550
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
5960
6061 for child in vdom_tree .get ("children" , []):
61- selected_options . extend ( _find_selected_options ( child , mutation ) )
62+ set_value_prop_on_select_element ( child )
6263
63- return selected_options
64+ return vdom_tree
6465
6566
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+
6894
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."""
6998 if not isinstance (vdom_tree , dict ):
7099 return vdom_tree
71100
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" :
76102 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+ )
84106
85107 for child in vdom_tree .get ("children" , []):
86- set_value_prop_on_select_element (child )
108+ intercept_anchor_links (child )
87109
88110 return vdom_tree
89111
90112
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+
91132def _normalize_prop_name (prop_name : str ) -> str :
92133 """Standardizes the prop name to be used in the component."""
93134 return REACT_PROP_SUBSTITUTIONS .get (prop_name , prop_name )
94135
95136
96- def react_props_set (string : str ) -> set [str ]:
137+ def _react_props_set (string : str ) -> set [str ]:
97138 """Extracts the props from a string of React props."""
98139 lines = string .strip ().split ("\n " )
99140 props = set ()
100141
101142 for line in lines :
102143 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-*" } :
104145 key , value = parts
105146 key = key .strip ()
106147 value = value .strip ()
@@ -130,35 +171,7 @@ def _add_on_change_event(event_func, vdom_tree: VdomDict) -> VdomDict:
130171 return vdom_tree
131172
132173
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 ):
162175 pass
163176
164177
@@ -495,7 +508,7 @@ def do_nothing_event(*args, **kwargs):
495508type: a string. Says whether the script is a classic script, ES module, or import map.
496509"""
497510
498- KNOWN_REACT_PROPS = react_props_set (
511+ KNOWN_REACT_PROPS = _react_props_set (
499512 SPECIAL_PROPS
500513 + STANDARD_PROPS
501514 + FORM_PROPS
0 commit comments