11from __future__ import annotations
22
3- from typing import TYPE_CHECKING
3+ from typing import TYPE_CHECKING , Any
44
55from reactpy .core .events import EventHandler , to_event_handler_function
66
1212
1313def convert_html_props_to_reactjs (vdom_tree : VdomDict ) -> VdomDict :
1414 """Transformation that standardizes the prop names to be used in the component."""
15- if not isinstance (vdom_tree , dict ):
16- return vdom_tree
17-
1815 # On each node, replace the 'attributes' key names with the standardized names.
1916 if "attributes" in vdom_tree :
2017 vdom_tree ["attributes" ] = {_normalize_prop_name (k ): v for k , v in vdom_tree ["attributes" ].items ()}
2118
22- for child in vdom_tree .get ("children" , []):
23- convert_html_props_to_reactjs (child )
24-
2519 return vdom_tree
2620
2721
2822def convert_textarea_children_to_prop (vdom_tree : VdomDict ) -> VdomDict :
2923 """Transformation that converts the text content of a <textarea> to the 'value' prop."""
30- if not isinstance (vdom_tree , dict ):
31- return vdom_tree
32-
33- # On each node, if the 'tagName' is 'textarea', move the text content to the 'value' prop.
24+ # If the current tag is <textarea>, move the text content to the 'value' prop.
3425 if vdom_tree ["tagName" ] == "textarea" and "children" in vdom_tree and vdom_tree ["children" ]:
3526 text_content = vdom_tree .pop ("children" )
3627 text_content = "" .join ([child for child in text_content if isinstance (child , str )])
3728 default_value = vdom_tree ["attributes" ].pop ("defaultValue" , "" )
3829 vdom_tree ["attributes" ]["defaultValue" ] = text_content or default_value
3930
40- for child in vdom_tree .get ("children" , []):
41- convert_textarea_children_to_prop (child )
42-
4331 return vdom_tree
4432
4533
4634def set_value_prop_on_select_element (vdom_tree : VdomDict ) -> VdomDict :
4735 """Use the `value` prop on <select> instead of setting `selected` on <option>."""
48- if not isinstance (vdom_tree , dict ):
49- return vdom_tree
50-
5136 # If the current tag is <select>, remove 'selected' prop from any <option> children and
5237 # instead set the 'value' prop on the <select> tag.
5338 if vdom_tree ["tagName" ] == "select" and "children" in vdom_tree :
@@ -58,48 +43,33 @@ def set_value_prop_on_select_element(vdom_tree: VdomDict) -> VdomDict:
5843 if selected_options and multiple_choice :
5944 vdom_tree ["attributes" ]["defaultValue" ] = selected_options
6045
61- for child in vdom_tree .get ("children" , []):
62- set_value_prop_on_select_element (child )
63-
6446 return vdom_tree
6547
6648
6749def ensure_input_elements_are_controlled (vdom_tree : VdomDict ) -> VdomDict :
6850 """Adds an onChange handler on form <input> elements, since ReactJS doesn't like uncontrolled inputs."""
69- if not isinstance (vdom_tree , dict ):
70- return vdom_tree
71-
7251 vdom_tree .setdefault ("eventHandlers" , {})
7352 if vdom_tree ["tagName" ] == "input" and "onChange" not in vdom_tree ["eventHandlers" ]:
7453 vdom_tree ["eventHandlers" ]["onChange" ] = EventHandler (to_event_handler_function (_do_nothing_event ))
7554
76- if "children" in vdom_tree :
77- for child in vdom_tree ["children" ]:
78- ensure_input_elements_are_controlled (child )
79-
8055 return vdom_tree
8156
8257
8358def intercept_anchor_links (vdom_tree : VdomDict ) -> VdomDict :
8459 """Intercepts anchor links and prevents the default behavior.
8560 This allows ReactPy-Router to handle the navigation instead of the browser."""
86- if not isinstance (vdom_tree , dict ):
87- return vdom_tree
88-
8961 if vdom_tree ["tagName" ] == "a" :
9062 vdom_tree .setdefault ("eventHandlers" , {})
9163 vdom_tree ["eventHandlers" ]["onClick" ] = EventHandler (
9264 to_event_handler_function (_do_nothing_event ), prevent_default = True
9365 )
9466
95- for child in vdom_tree .get ("children" , []):
96- intercept_anchor_links (child )
97-
9867 return vdom_tree
9968
10069
101- def _find_selected_options (vdom_tree : VdomDict ) -> list [str ]:
102- """Recursively iterate through the tree of dictionaries to find an <option> with the 'selected' prop."""
70+ def _find_selected_options (vdom_tree : VdomDict | Any ) -> list [str ]:
71+ """Recursively iterate through the tree to find all <option> tags with the 'selected' prop.
72+ Removes the 'selected' prop and returns a list of the 'value' prop of each selected <option>."""
10373 if not isinstance (vdom_tree , dict ):
10474 return []
10575
@@ -122,8 +92,8 @@ def _normalize_prop_name(prop_name: str) -> str:
12292 return REACT_PROP_SUBSTITUTIONS .get (prop_name , prop_name )
12393
12494
125- def _react_props_set (string : str ) -> set [str ]:
126- """Extracts the props from a string of React props."""
95+ def _parse_react_props (string : str ) -> set [str ]:
96+ """Extracts the props from a string of React props (copy-pasted from ReactJS docs) ."""
12797 lines = string .strip ().split ("\n " )
12898 props = set ()
12999
@@ -138,34 +108,12 @@ def _react_props_set(string: str) -> set[str]:
138108 return props
139109
140110
141- def _add_on_change_event (event_func , vdom_tree : VdomDict ) -> VdomDict :
142- """Recursively adds an onChange event handler to all elements in the tree."""
143- if not isinstance (vdom_tree , dict ):
144- return vdom_tree
145-
146- vdom_tree .setdefault ("eventHandlers" , {})
147- if vdom_tree ["tagName" ] in {"input" , "textarea" }:
148- if "onChange" in vdom_tree ["eventHandlers" ]:
149- pass
150- elif isinstance (event_func , EventHandler ):
151- vdom_tree ["eventHandlers" ]["onChange" ] = event_func
152- else :
153- vdom_tree ["eventHandlers" ]["onChange" ] = EventHandler (to_event_handler_function (event_func ))
154-
155- if "children" in vdom_tree :
156- for child in vdom_tree ["children" ]:
157- _add_on_change_event (event_func , child )
158-
159- return vdom_tree
160-
161-
162111def _do_nothing_event (* args , ** kwargs ):
163- pass
112+ """A placeholder event function that does nothing."""
164113
165114
166- # TODO: Create some kind of script that will generate this during build time, rather than hardcoding it.
115+ # TODO: Create some kind of script that will generate the react docs copy-pasta during build time, rather than hardcoding it.
167116# https://react.dev/reference/react-dom/components/common#common-props
168-
169117SPECIAL_PROPS = r"""
170118children: A React node (an element, a string, a number, a portal, an empty node like null, undefined and booleans, or an array of other React nodes). Specifies the content inside the component. When you use JSX, you will usually specify the children prop implicitly by nesting tags like <div><span /></div>.
171119dangerouslySetInnerHTML: An object of the form { __html: '<p>some html</p>' } with a raw HTML string inside. Overrides the innerHTML property of the DOM node and displays the passed HTML inside. This should be used with extreme caution! If the HTML inside isn't trusted (for example, if it's based on user data), you risk introducing an XSS vulnerability. Read more about using dangerouslySetInnerHTML.
@@ -496,7 +444,7 @@ def _do_nothing_event(*args, **kwargs):
496444type: a string. Says whether the script is a classic script, ES module, or import map.
497445"""
498446
499- KNOWN_REACT_PROPS = _react_props_set (
447+ KNOWN_REACT_PROPS = _parse_react_props (
500448 SPECIAL_PROPS
501449 + STANDARD_PROPS
502450 + FORM_PROPS
0 commit comments