reflex 0.3.2a1__py3-none-any.whl → 0.3.3a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (56) hide show
  1. reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +20 -3
  2. reflex/.templates/web/next.config.js +1 -0
  3. reflex/.templates/web/utils/helpers/range.js +43 -0
  4. reflex/.templates/web/utils/state.js +10 -6
  5. reflex/__init__.py +312 -40
  6. reflex/__init__.pyi +477 -0
  7. reflex/compiler/compiler.py +3 -0
  8. reflex/components/__init__.py +138 -138
  9. reflex/components/component.py +29 -22
  10. reflex/components/datadisplay/__init__.py +3 -1
  11. reflex/components/datadisplay/code.py +388 -14
  12. reflex/components/datadisplay/code.pyi +1146 -10
  13. reflex/components/forms/button.py +3 -0
  14. reflex/components/forms/checkbox.py +3 -0
  15. reflex/components/forms/form.py +90 -27
  16. reflex/components/forms/input.py +3 -0
  17. reflex/components/forms/numberinput.py +3 -0
  18. reflex/components/forms/pininput.py +77 -21
  19. reflex/components/forms/radio.py +3 -0
  20. reflex/components/forms/rangeslider.py +3 -0
  21. reflex/components/forms/select.py +3 -0
  22. reflex/components/forms/slider.py +3 -0
  23. reflex/components/forms/switch.py +3 -0
  24. reflex/components/forms/textarea.py +3 -0
  25. reflex/components/layout/foreach.py +12 -6
  26. reflex/components/libs/chakra.py +2 -0
  27. reflex/components/libs/chakra.pyi +323 -24
  28. reflex/components/tags/iter_tag.py +18 -18
  29. reflex/components/tags/tag.py +3 -2
  30. reflex/components/typography/markdown.py +10 -0
  31. reflex/config.py +12 -0
  32. reflex/constants/installer.py +4 -4
  33. reflex/event.py +4 -0
  34. reflex/page.py +3 -4
  35. reflex/page.pyi +17 -0
  36. reflex/reflex.py +3 -0
  37. reflex/state.py +31 -12
  38. reflex/testing.py +1 -1
  39. reflex/utils/build.py +24 -19
  40. reflex/utils/console.py +5 -1
  41. reflex/utils/format.py +26 -9
  42. reflex/utils/prerequisites.py +27 -28
  43. reflex/utils/processes.py +5 -4
  44. reflex/vars.py +80 -12
  45. reflex/vars.pyi +7 -0
  46. {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/METADATA +3 -2
  47. {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/RECORD +50 -53
  48. reflex/.templates/web/.pytest_cache/.gitignore +0 -2
  49. reflex/.templates/web/.pytest_cache/CACHEDIR.TAG +0 -4
  50. reflex/.templates/web/.pytest_cache/README.md +0 -8
  51. reflex/.templates/web/.pytest_cache/v/cache/nodeids +0 -1
  52. reflex/.templates/web/.pytest_cache/v/cache/stepwise +0 -1
  53. reflex/.templates/web/styles/code/prism.js +0 -1015
  54. {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/LICENSE +0 -0
  55. {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/WHEEL +0 -0
  56. {reflex-0.3.2a1.dist-info → reflex-0.3.3a1.dist-info}/entry_points.txt +0 -0
@@ -56,6 +56,9 @@ class Button(ChakraComponent):
56
56
  # Components that are not allowed as children.
57
57
  invalid_children: List[str] = ["Button", "MenuButton"]
58
58
 
59
+ # The name of the form field
60
+ name: Var[str]
61
+
59
62
 
60
63
  class ButtonGroup(ChakraComponent):
61
64
  """A group of buttons."""
@@ -50,6 +50,9 @@ class Checkbox(ChakraComponent):
50
50
  # The name of the input field in a checkbox (Useful for form submission).
51
51
  name: Var[str]
52
52
 
53
+ # The value of the input field when checked (use is_checked prop for a bool)
54
+ value: Var[str] = Var.create(True) # type: ignore
55
+
53
56
  # The spacing between the checkbox and its label text (0.5rem)
54
57
  spacing: Var[str]
55
58
 
@@ -1,13 +1,36 @@
1
1
  """Form components."""
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Any, Callable, Dict, List
4
+ from typing import Any, Dict
5
+
6
+ from jinja2 import Environment
5
7
 
6
8
  from reflex.components.component import Component
7
9
  from reflex.components.libs.chakra import ChakraComponent
10
+ from reflex.components.tags import Tag
8
11
  from reflex.constants import EventTriggers
9
- from reflex.event import EventChain, EventHandler, EventSpec
10
- from reflex.vars import Var
12
+ from reflex.event import EventChain
13
+ from reflex.utils import imports
14
+ from reflex.utils.format import format_event_chain, to_camel_case
15
+ from reflex.utils.serializers import serialize
16
+ from reflex.vars import BaseVar, Var, get_unique_variable_name
17
+
18
+ FORM_DATA = Var.create("form_data")
19
+ HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
20
+ """
21
+ const handleSubmit{{ handle_submit_unique_name }} = useCallback((ev) => {
22
+ const $form = ev.target
23
+ ev.preventDefault()
24
+ const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}}
25
+
26
+ {{ on_submit_event_chain }}
27
+
28
+ if ({{ reset_on_submit }}) {
29
+ $form.reset()
30
+ }
31
+ })
32
+ """
33
+ )
11
34
 
12
35
 
13
36
  class Form(ChakraComponent):
@@ -18,35 +41,68 @@ class Form(ChakraComponent):
18
41
  # What the form renders to.
19
42
  as_: Var[str] = "form" # type: ignore
20
43
 
21
- def _create_event_chain(
22
- self,
23
- event_trigger: str,
24
- value: Var
25
- | EventHandler
26
- | EventSpec
27
- | List[EventHandler | EventSpec]
28
- | Callable[..., Any],
29
- ) -> EventChain | Var:
30
- """Override the event chain creation to preventDefault for on_submit.
44
+ # If true, the form will be cleared after submit.
45
+ reset_on_submit: Var[bool] = False # type: ignore
31
46
 
32
- Args:
33
- event_trigger: The event trigger.
34
- value: The value of the event trigger.
47
+ # The name used to make this form's submit handler function unique
48
+ handle_submit_unique_name: Var[str]
35
49
 
36
- Returns:
37
- The event chain.
38
- """
39
- chain = super()._create_event_chain(event_trigger, value)
40
- if event_trigger == EventTriggers.ON_SUBMIT and isinstance(chain, EventChain):
41
- return chain.prevent_default
42
- return chain
50
+ @classmethod
51
+ def create(cls, *children, **props) -> Component:
52
+ """Create a form component.
43
53
 
44
- def get_event_triggers(self) -> Dict[str, Any]:
45
- """Get the event triggers that pass the component's value to the handler.
54
+ Args:
55
+ *children: The children of the form.
56
+ **props: The properties of the form.
46
57
 
47
58
  Returns:
48
- A dict mapping the event trigger to the var that is passed to the handler.
59
+ The form component.
49
60
  """
61
+ if "handle_submit_unique_name" not in props:
62
+ props["handle_submit_unique_name"] = get_unique_variable_name()
63
+ return super().create(*children, **props)
64
+
65
+ def _get_imports(self) -> imports.ImportDict:
66
+ return imports.merge_imports(
67
+ super()._get_imports(),
68
+ {"react": {imports.ImportVar(tag="useCallback")}},
69
+ )
70
+
71
+ def _get_hooks(self) -> str | None:
72
+ if EventTriggers.ON_SUBMIT not in self.event_triggers:
73
+ return
74
+ return HANDLE_SUBMIT_JS_JINJA2.render(
75
+ handle_submit_unique_name=self.handle_submit_unique_name,
76
+ form_data=FORM_DATA,
77
+ field_ref_mapping=serialize(self._get_form_refs()),
78
+ on_submit_event_chain=format_event_chain(
79
+ self.event_triggers[EventTriggers.ON_SUBMIT]
80
+ ),
81
+ reset_on_submit=self.reset_on_submit,
82
+ )
83
+
84
+ def _render(self) -> Tag:
85
+ render_tag = (
86
+ super()
87
+ ._render()
88
+ .remove_props(
89
+ "reset_on_submit",
90
+ "handle_submit_unique_name",
91
+ to_camel_case(EventTriggers.ON_SUBMIT),
92
+ )
93
+ )
94
+ if EventTriggers.ON_SUBMIT in self.event_triggers:
95
+ render_tag.add_props(
96
+ **{
97
+ EventTriggers.ON_SUBMIT: BaseVar(
98
+ _var_name=f"handleSubmit{self.handle_submit_unique_name}",
99
+ _var_type=EventChain,
100
+ )
101
+ }
102
+ )
103
+ return render_tag
104
+
105
+ def _get_form_refs(self) -> Dict[str, Any]:
50
106
  # Send all the input refs to the handler.
51
107
  form_refs = {}
52
108
  for ref in self.get_refs():
@@ -60,10 +116,17 @@ class Form(ChakraComponent):
60
116
  form_refs[ref[4:]] = Var.create(
61
117
  f"getRefValue({ref})", _var_is_local=False
62
118
  )
119
+ return form_refs
63
120
 
121
+ def get_event_triggers(self) -> Dict[str, Any]:
122
+ """Get the event triggers that pass the component's value to the handler.
123
+
124
+ Returns:
125
+ A dict mapping the event trigger to the var that is passed to the handler.
126
+ """
64
127
  return {
65
128
  **super().get_event_triggers(),
66
- EventTriggers.ON_SUBMIT: lambda e0: [form_refs],
129
+ EventTriggers.ON_SUBMIT: lambda e0: [FORM_DATA],
67
130
  }
68
131
 
69
132
 
@@ -55,6 +55,9 @@ class Input(ChakraComponent):
55
55
  # "lg" | "md" | "sm" | "xs"
56
56
  size: Var[LiteralButtonSize]
57
57
 
58
+ # The name of the form field
59
+ name: Var[str]
60
+
58
61
  def _get_imports(self) -> imports.ImportDict:
59
62
  return imports.merge_imports(
60
63
  super()._get_imports(),
@@ -68,6 +68,9 @@ class NumberInput(ChakraComponent):
68
68
  # "outline" | "filled" | "flushed" | "unstyled"
69
69
  variant: Var[LiteralInputVariant]
70
70
 
71
+ # The name of the form field
72
+ name: Var[str]
73
+
71
74
  def get_event_triggers(self) -> Dict[str, Any]:
72
75
  """Get the event triggers that pass the component's value to the handler.
73
76
 
@@ -4,10 +4,11 @@ from __future__ import annotations
4
4
  from typing import Any, Optional, Union
5
5
 
6
6
  from reflex.components.component import Component
7
- from reflex.components.layout import Foreach
8
7
  from reflex.components.libs.chakra import ChakraComponent, LiteralInputVariant
8
+ from reflex.components.tags.tag import Tag
9
9
  from reflex.constants import EventTriggers
10
10
  from reflex.utils import format
11
+ from reflex.utils.imports import ImportDict, merge_imports
11
12
  from reflex.vars import Var
12
13
 
13
14
 
@@ -58,6 +59,20 @@ class PinInput(ChakraComponent):
58
59
  # "outline" | "flushed" | "filled" | "unstyled"
59
60
  variant: Var[LiteralInputVariant]
60
61
 
62
+ # The name of the form field
63
+ name: Var[str]
64
+
65
+ def _get_imports(self) -> ImportDict:
66
+ """Include PinInputField explicitly because it may not be a child component at compile time.
67
+
68
+ Returns:
69
+ The merged import dict.
70
+ """
71
+ return merge_imports(
72
+ super()._get_imports(),
73
+ PinInputField().get_imports(), # type: ignore
74
+ )
75
+
61
76
  def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
62
77
  """Get the event triggers that pass the component's value to the handler.
63
78
 
@@ -70,13 +85,24 @@ class PinInput(ChakraComponent):
70
85
  EventTriggers.ON_COMPLETE: lambda e0: [e0],
71
86
  }
72
87
 
73
- def get_ref(self):
74
- """Return a reference because we actually attached the ref to the PinInputFields.
88
+ def get_ref(self) -> str | None:
89
+ """Override ref handling to handle array refs.
90
+
91
+ PinInputFields may be created dynamically, so it's not possible
92
+ to compute their ref at compile time, so we return a cheating
93
+ guess if the id is specified.
94
+
95
+ The `ref` for this outer component will always be stripped off, so what
96
+ is returned here only matters for form ref collection purposes.
75
97
 
76
98
  Returns:
77
99
  None.
78
100
  """
79
- return None
101
+ if any(isinstance(c, PinInputField) for c in self.children):
102
+ return None
103
+ if self.id:
104
+ return format.format_array_ref(self.id, idx=self.length)
105
+ return super().get_ref()
80
106
 
81
107
  def _get_ref_hook(self) -> Optional[str]:
82
108
  """Override the base _get_ref_hook to handle array refs.
@@ -86,10 +112,22 @@ class PinInput(ChakraComponent):
86
112
  """
87
113
  if self.id:
88
114
  ref = format.format_array_ref(self.id, None)
115
+ refs_declaration = Var.range(self.length).foreach(
116
+ lambda: Var.create_safe("useRef(null)", _var_is_string=False),
117
+ )
118
+ refs_declaration._var_is_local = True
89
119
  if ref:
90
- return f"const {ref} = Array.from({{length:{self.length}}}, () => useRef(null));"
120
+ return f"const {ref} = {refs_declaration}"
91
121
  return super()._get_ref_hook()
92
122
 
123
+ def _render(self) -> Tag:
124
+ """Override the base _render to remove the fake get_ref.
125
+
126
+ Returns:
127
+ The rendered component.
128
+ """
129
+ return super()._render().remove_props("ref")
130
+
93
131
  @classmethod
94
132
  def create(cls, *children, **props) -> Component:
95
133
  """Create a pin input component.
@@ -104,22 +142,17 @@ class PinInput(ChakraComponent):
104
142
  Returns:
105
143
  The pin input component.
106
144
  """
107
- if not children and "length" in props:
108
- _id = props.get("id", None)
109
- length = props["length"]
110
- if _id:
111
- children = [
112
- Foreach.create(
113
- list(range(length)), # type: ignore
114
- lambda ref, i: PinInputField.create(
115
- key=i,
116
- id=_id,
117
- index=i,
118
- ),
119
- )
120
- ]
121
- else:
122
- children = [PinInputField()] * length
145
+ if children:
146
+ props.pop("length", None)
147
+ elif "length" in props:
148
+ field_props = {}
149
+ if "id" in props:
150
+ field_props["id"] = props["id"]
151
+ if "name" in props:
152
+ field_props["name"] = props["name"]
153
+ children = [
154
+ PinInputField.for_length(props["length"], **field_props),
155
+ ]
123
156
  return super().create(*children, **props)
124
157
 
125
158
 
@@ -132,6 +165,29 @@ class PinInputField(ChakraComponent):
132
165
  # Default to None because it is assigned by PinInput when created.
133
166
  index: Optional[Var[int]] = None
134
167
 
168
+ # The name of the form field
169
+ name: Var[str]
170
+
171
+ @classmethod
172
+ def for_length(cls, length: Var | int, **props) -> Var:
173
+ """Create a PinInputField for a PinInput with a given length.
174
+
175
+ Args:
176
+ length: The length of the PinInput.
177
+ props: The props of each PinInputField (name will become indexed).
178
+
179
+ Returns:
180
+ The PinInputField.
181
+ """
182
+ name = props.get("name")
183
+
184
+ def _create(i):
185
+ if name is not None:
186
+ props["name"] = f"{name}-{i}"
187
+ return PinInputField.create(**props, index=i, key=i)
188
+
189
+ return Var.range(length).foreach(_create) # type: ignore
190
+
135
191
  def _get_ref_hook(self) -> Optional[str]:
136
192
  return None
137
193
 
@@ -23,6 +23,9 @@ class RadioGroup(ChakraComponent):
23
23
  # The default value.
24
24
  default_value: Var[Any]
25
25
 
26
+ # The name of the form field
27
+ name: Var[str]
28
+
26
29
  def get_event_triggers(self) -> Dict[str, Union[Var, Any]]:
27
30
  """Get the event triggers that pass the component's value to the handler.
28
31
 
@@ -45,6 +45,9 @@ class RangeSlider(ChakraComponent):
45
45
  # The minimum distance between slider thumbs. Useful for preventing the thumbs from being too close together.
46
46
  min_steps_between_thumbs: Var[int]
47
47
 
48
+ # The name of the form field
49
+ name: Var[str]
50
+
48
51
  def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
49
52
  """Get the event triggers that pass the component's value to the handler.
50
53
 
@@ -46,6 +46,9 @@ class Select(ChakraComponent):
46
46
  # The size of the select.
47
47
  size: Var[str]
48
48
 
49
+ # The name of the form field
50
+ name: Var[str]
51
+
49
52
  def get_event_triggers(self) -> Dict[str, Union[Var, Any]]:
50
53
  """Get the event triggers that pass the component's value to the handler.
51
54
 
@@ -66,6 +66,9 @@ class Slider(ChakraComponent):
66
66
  # Maximum width of the slider.
67
67
  max_w: Var[str]
68
68
 
69
+ # The name of the form field
70
+ name: Var[str]
71
+
69
72
  def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
70
73
  """Get the event triggers that pass the component's value to the handler.
71
74
 
@@ -34,6 +34,9 @@ class Switch(ChakraComponent):
34
34
  # The name of the input field in a switch (Useful for form submission).
35
35
  name: Var[str]
36
36
 
37
+ # The value of the input field when checked (use is_checked prop for a bool)
38
+ value: Var[str] = Var.create(True) # type: ignore
39
+
37
40
  # The spacing between the switch and its label text (0.5rem)
38
41
  spacing: Var[str]
39
42
 
@@ -45,6 +45,9 @@ class TextArea(ChakraComponent):
45
45
  # "outline" | "filled" | "flushed" | "unstyled"
46
46
  variant: Var[LiteralInputVariant]
47
47
 
48
+ # The name of the form field
49
+ name: Var[str]
50
+
48
51
  def get_event_triggers(self) -> dict[str, Union[Var, Any]]:
49
52
  """Get the event triggers that pass the component's value to the handler.
50
53
 
@@ -1,6 +1,7 @@
1
1
  """Create a list of components from an iterable."""
2
2
  from __future__ import annotations
3
3
 
4
+ import typing
4
5
  from typing import Any, Callable, Iterable
5
6
 
6
7
  from reflex.components.component import Component
@@ -47,15 +48,20 @@ class Foreach(Component):
47
48
  f"Could not foreach over var of type Any. (If you are trying to foreach over a state var, add a type annotation to the var.)"
48
49
  )
49
50
  arg = BaseVar(_var_name="_", _var_type=type_, _var_is_local=True)
51
+ comp = IterTag(iterable=iterable, render_fn=render_fn).render_component(arg)
50
52
  return cls(
51
53
  iterable=iterable,
52
54
  render_fn=render_fn,
53
- children=[IterTag.render_component(render_fn, arg=arg)],
55
+ children=[comp],
54
56
  **props,
55
57
  )
56
58
 
57
59
  def _render(self) -> IterTag:
58
- return IterTag(iterable=self.iterable, render_fn=self.render_fn)
60
+ return IterTag(
61
+ iterable=self.iterable,
62
+ render_fn=self.render_fn,
63
+ index_var_name=get_unique_variable_name(),
64
+ )
59
65
 
60
66
  def render(self):
61
67
  """Render the component.
@@ -66,9 +72,9 @@ class Foreach(Component):
66
72
  tag = self._render()
67
73
  try:
68
74
  type_ = (
69
- self.iterable._var_type
70
- if self.iterable._var_type.mro()[0] == dict
71
- else self.iterable._var_type.__args__[0]
75
+ tag.iterable._var_type
76
+ if tag.iterable._var_type.mro()[0] == dict
77
+ else typing.get_args(tag.iterable._var_type)[0]
72
78
  )
73
79
  except Exception:
74
80
  type_ = Any
@@ -77,7 +83,7 @@ class Foreach(Component):
77
83
  _var_type=type_,
78
84
  )
79
85
  index_arg = tag.get_index_var_arg()
80
- component = tag.render_component(self.render_fn, arg)
86
+ component = tag.render_component(arg)
81
87
  return dict(
82
88
  tag.add_props(
83
89
  **self.event_triggers,
@@ -122,6 +122,8 @@ LiteralColorScheme = Literal[
122
122
  LiteralVariant = Literal["solid", "subtle", "outline"]
123
123
  LiteralDividerVariant = Literal["solid", "dashed"]
124
124
  LiteralTheme = Literal["light", "dark"]
125
+
126
+
125
127
  LiteralTagColorScheme = Literal[
126
128
  "gray",
127
129
  "red",