reflex 0.7.14a5__py3-none-any.whl → 0.8.0a1__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 (205) hide show
  1. reflex/.templates/jinja/app/rxconfig.py.jinja2 +4 -1
  2. reflex/.templates/jinja/web/package.json.jinja2 +1 -1
  3. reflex/.templates/jinja/web/pages/_app.js.jinja2 +16 -10
  4. reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
  5. reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
  6. reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -6
  7. reflex/.templates/web/app/entry.client.js +8 -0
  8. reflex/.templates/web/app/routes.js +10 -0
  9. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -32
  10. reflex/.templates/web/postcss.config.js +1 -1
  11. reflex/.templates/web/react-router.config.js +6 -0
  12. reflex/.templates/web/utils/client_side_routing.js +21 -19
  13. reflex/.templates/web/utils/react-theme.js +89 -0
  14. reflex/.templates/web/utils/state.js +155 -67
  15. reflex/.templates/web/vite.config.js +32 -0
  16. reflex/__init__.py +1 -6
  17. reflex/__init__.pyi +0 -4
  18. reflex/app.py +69 -132
  19. reflex/base.py +1 -87
  20. reflex/compiler/compiler.py +40 -3
  21. reflex/compiler/utils.py +54 -28
  22. reflex/components/__init__.py +0 -2
  23. reflex/components/__init__.pyi +0 -3
  24. reflex/components/base/__init__.py +1 -5
  25. reflex/components/base/__init__.pyi +4 -6
  26. reflex/components/base/app_wrap.pyi +5 -4
  27. reflex/components/base/body.pyi +5 -4
  28. reflex/components/base/document.py +18 -14
  29. reflex/components/base/document.pyi +83 -27
  30. reflex/components/base/error_boundary.pyi +5 -4
  31. reflex/components/base/fragment.pyi +5 -4
  32. reflex/components/base/link.pyi +9 -7
  33. reflex/components/base/meta.pyi +17 -13
  34. reflex/components/base/script.py +60 -58
  35. reflex/components/base/script.pyi +246 -31
  36. reflex/components/base/strict_mode.pyi +5 -4
  37. reflex/components/component.py +109 -194
  38. reflex/components/core/__init__.py +1 -0
  39. reflex/components/core/__init__.pyi +1 -0
  40. reflex/components/core/auto_scroll.pyi +5 -4
  41. reflex/components/core/banner.pyi +25 -19
  42. reflex/components/core/client_side_routing.py +7 -6
  43. reflex/components/core/client_side_routing.pyi +6 -56
  44. reflex/components/core/clipboard.pyi +5 -4
  45. reflex/components/core/debounce.py +1 -0
  46. reflex/components/core/debounce.pyi +5 -4
  47. reflex/components/core/foreach.py +3 -2
  48. reflex/components/core/helmet.py +14 -0
  49. reflex/components/{next/base.pyi → core/helmet.pyi} +10 -7
  50. reflex/components/core/html.pyi +5 -4
  51. reflex/components/core/sticky.pyi +17 -13
  52. reflex/components/core/upload.py +2 -1
  53. reflex/components/core/upload.pyi +21 -16
  54. reflex/components/datadisplay/code.pyi +9 -7
  55. reflex/components/datadisplay/dataeditor.pyi +5 -4
  56. reflex/components/datadisplay/shiki_code_block.pyi +13 -10
  57. reflex/components/dynamic.py +4 -5
  58. reflex/components/el/element.pyi +5 -4
  59. reflex/components/el/elements/base.pyi +5 -4
  60. reflex/components/el/elements/forms.pyi +69 -52
  61. reflex/components/el/elements/inline.pyi +113 -85
  62. reflex/components/el/elements/media.pyi +105 -79
  63. reflex/components/el/elements/metadata.pyi +25 -19
  64. reflex/components/el/elements/other.pyi +29 -22
  65. reflex/components/el/elements/scripts.pyi +13 -10
  66. reflex/components/el/elements/sectioning.pyi +61 -46
  67. reflex/components/el/elements/tables.pyi +41 -31
  68. reflex/components/el/elements/typography.pyi +61 -46
  69. reflex/components/field.py +175 -0
  70. reflex/components/gridjs/datatable.py +2 -2
  71. reflex/components/gridjs/datatable.pyi +11 -9
  72. reflex/components/lucide/icon.py +6 -2
  73. reflex/components/lucide/icon.pyi +15 -10
  74. reflex/components/markdown/markdown.pyi +5 -4
  75. reflex/components/moment/moment.pyi +5 -4
  76. reflex/components/plotly/plotly.pyi +19 -10
  77. reflex/components/props.py +376 -27
  78. reflex/components/radix/primitives/accordion.py +8 -1
  79. reflex/components/radix/primitives/accordion.pyi +29 -22
  80. reflex/components/radix/primitives/base.pyi +9 -7
  81. reflex/components/radix/primitives/drawer.pyi +45 -34
  82. reflex/components/radix/primitives/form.pyi +41 -31
  83. reflex/components/radix/primitives/progress.pyi +21 -16
  84. reflex/components/radix/primitives/slider.pyi +21 -16
  85. reflex/components/radix/themes/base.py +3 -3
  86. reflex/components/radix/themes/base.pyi +33 -25
  87. reflex/components/radix/themes/color_mode.pyi +13 -10
  88. reflex/components/radix/themes/components/alert_dialog.pyi +29 -22
  89. reflex/components/radix/themes/components/aspect_ratio.pyi +5 -4
  90. reflex/components/radix/themes/components/avatar.pyi +5 -4
  91. reflex/components/radix/themes/components/badge.pyi +5 -4
  92. reflex/components/radix/themes/components/button.pyi +5 -4
  93. reflex/components/radix/themes/components/callout.pyi +21 -16
  94. reflex/components/radix/themes/components/card.pyi +5 -4
  95. reflex/components/radix/themes/components/checkbox.pyi +13 -10
  96. reflex/components/radix/themes/components/checkbox_cards.pyi +9 -7
  97. reflex/components/radix/themes/components/checkbox_group.pyi +9 -7
  98. reflex/components/radix/themes/components/context_menu.pyi +53 -40
  99. reflex/components/radix/themes/components/data_list.pyi +17 -13
  100. reflex/components/radix/themes/components/dialog.pyi +29 -22
  101. reflex/components/radix/themes/components/dropdown_menu.pyi +33 -25
  102. reflex/components/radix/themes/components/hover_card.pyi +17 -13
  103. reflex/components/radix/themes/components/icon_button.pyi +5 -4
  104. reflex/components/radix/themes/components/inset.pyi +5 -4
  105. reflex/components/radix/themes/components/popover.pyi +17 -13
  106. reflex/components/radix/themes/components/progress.pyi +5 -4
  107. reflex/components/radix/themes/components/radio.pyi +5 -4
  108. reflex/components/radix/themes/components/radio_cards.pyi +9 -7
  109. reflex/components/radix/themes/components/radio_group.pyi +17 -13
  110. reflex/components/radix/themes/components/scroll_area.pyi +5 -4
  111. reflex/components/radix/themes/components/segmented_control.pyi +9 -7
  112. reflex/components/radix/themes/components/select.pyi +37 -28
  113. reflex/components/radix/themes/components/separator.pyi +5 -4
  114. reflex/components/radix/themes/components/skeleton.pyi +5 -4
  115. reflex/components/radix/themes/components/slider.pyi +5 -4
  116. reflex/components/radix/themes/components/spinner.pyi +5 -4
  117. reflex/components/radix/themes/components/switch.pyi +5 -4
  118. reflex/components/radix/themes/components/table.pyi +29 -22
  119. reflex/components/radix/themes/components/tabs.pyi +21 -16
  120. reflex/components/radix/themes/components/text_area.pyi +5 -4
  121. reflex/components/radix/themes/components/text_field.pyi +13 -10
  122. reflex/components/radix/themes/components/tooltip.pyi +5 -4
  123. reflex/components/radix/themes/layout/base.pyi +5 -4
  124. reflex/components/radix/themes/layout/box.pyi +5 -4
  125. reflex/components/radix/themes/layout/center.pyi +5 -4
  126. reflex/components/radix/themes/layout/container.pyi +5 -4
  127. reflex/components/radix/themes/layout/flex.pyi +5 -4
  128. reflex/components/radix/themes/layout/grid.pyi +5 -4
  129. reflex/components/radix/themes/layout/list.pyi +21 -16
  130. reflex/components/radix/themes/layout/section.pyi +5 -4
  131. reflex/components/radix/themes/layout/spacer.pyi +5 -4
  132. reflex/components/radix/themes/layout/stack.pyi +13 -10
  133. reflex/components/radix/themes/typography/blockquote.pyi +5 -4
  134. reflex/components/radix/themes/typography/code.pyi +5 -4
  135. reflex/components/radix/themes/typography/heading.pyi +5 -4
  136. reflex/components/radix/themes/typography/link.py +42 -9
  137. reflex/components/radix/themes/typography/link.pyi +311 -6
  138. reflex/components/radix/themes/typography/text.pyi +29 -22
  139. reflex/components/react_player/audio.pyi +5 -4
  140. reflex/components/react_player/react_player.pyi +5 -4
  141. reflex/components/react_player/video.pyi +5 -4
  142. reflex/components/recharts/cartesian.py +2 -1
  143. reflex/components/recharts/cartesian.pyi +65 -46
  144. reflex/components/recharts/charts.py +4 -2
  145. reflex/components/recharts/charts.pyi +36 -24
  146. reflex/components/recharts/general.pyi +24 -18
  147. reflex/components/recharts/polar.py +8 -4
  148. reflex/components/recharts/polar.pyi +16 -10
  149. reflex/components/recharts/recharts.pyi +9 -7
  150. reflex/components/sonner/toast.py +2 -2
  151. reflex/components/sonner/toast.pyi +7 -6
  152. reflex/config.py +3 -77
  153. reflex/constants/__init__.py +1 -0
  154. reflex/constants/base.py +38 -8
  155. reflex/constants/compiler.py +4 -2
  156. reflex/constants/event.py +1 -0
  157. reflex/constants/installer.py +23 -16
  158. reflex/constants/state.py +2 -0
  159. reflex/custom_components/custom_components.py +0 -14
  160. reflex/environment.py +1 -1
  161. reflex/event.py +178 -81
  162. reflex/experimental/__init__.py +0 -30
  163. reflex/istate/proxy.py +5 -3
  164. reflex/page.py +0 -27
  165. reflex/plugins/__init__.py +3 -2
  166. reflex/plugins/base.py +5 -1
  167. reflex/plugins/shared_tailwind.py +158 -0
  168. reflex/plugins/sitemap.py +206 -0
  169. reflex/plugins/tailwind_v3.py +13 -106
  170. reflex/plugins/tailwind_v4.py +15 -108
  171. reflex/reflex.py +1 -0
  172. reflex/state.py +134 -140
  173. reflex/testing.py +57 -9
  174. reflex/utils/build.py +9 -69
  175. reflex/utils/exec.py +59 -161
  176. reflex/utils/export.py +1 -1
  177. reflex/utils/imports.py +0 -4
  178. reflex/utils/misc.py +28 -0
  179. reflex/utils/prerequisites.py +62 -59
  180. reflex/utils/processes.py +6 -6
  181. reflex/utils/pyi_generator.py +21 -9
  182. reflex/utils/serializers.py +14 -1
  183. reflex/utils/types.py +196 -61
  184. reflex/vars/__init__.py +2 -0
  185. reflex/vars/base.py +367 -134
  186. {reflex-0.7.14a5.dist-info → reflex-0.8.0a1.dist-info}/METADATA +12 -5
  187. {reflex-0.7.14a5.dist-info → reflex-0.8.0a1.dist-info}/RECORD +190 -196
  188. reflex/.templates/web/next.config.js +0 -7
  189. reflex/components/base/head.py +0 -20
  190. reflex/components/base/head.pyi +0 -116
  191. reflex/components/next/__init__.py +0 -10
  192. reflex/components/next/base.py +0 -7
  193. reflex/components/next/image.py +0 -117
  194. reflex/components/next/image.pyi +0 -94
  195. reflex/components/next/link.py +0 -20
  196. reflex/components/next/link.pyi +0 -67
  197. reflex/components/next/video.py +0 -38
  198. reflex/components/next/video.pyi +0 -68
  199. reflex/components/suneditor/__init__.py +0 -5
  200. reflex/components/suneditor/editor.py +0 -269
  201. reflex/components/suneditor/editor.pyi +0 -199
  202. reflex/experimental/layout.py +0 -254
  203. {reflex-0.7.14a5.dist-info → reflex-0.8.0a1.dist-info}/WHEEL +0 -0
  204. {reflex-0.7.14a5.dist-info → reflex-0.8.0a1.dist-info}/entry_points.txt +0 -0
  205. {reflex-0.7.14a5.dist-info → reflex-0.8.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -2,17 +2,280 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from pydantic import ValidationError
5
+ from collections.abc import Callable
6
+ from dataclasses import _MISSING_TYPE, MISSING
7
+ from typing import Any, TypeVar, get_args, get_origin
6
8
 
7
- from reflex.base import Base
9
+ from typing_extensions import dataclass_transform
10
+
11
+ from reflex.components.field import BaseField, FieldBasedMeta
8
12
  from reflex.utils import format
9
13
  from reflex.utils.exceptions import InvalidPropValueError
14
+ from reflex.utils.serializers import serializer
15
+ from reflex.utils.types import is_union
10
16
  from reflex.vars.object import LiteralObjectVar
11
17
 
18
+ PROPS_FIELD_TYPE = TypeVar("PROPS_FIELD_TYPE")
19
+
20
+
21
+ def _get_props_subclass(field_type: Any) -> type | None:
22
+ """Extract the Props subclass from a field type annotation.
23
+
24
+ Args:
25
+ field_type: The type annotation to check.
26
+
27
+ Returns:
28
+ The Props subclass if found, None otherwise.
29
+ """
30
+ from reflex.utils.types import typehint_issubclass
31
+
32
+ # For direct class types, we can return them directly if they're Props subclasses
33
+ if isinstance(field_type, type):
34
+ return field_type if typehint_issubclass(field_type, PropsBase) else None
35
+
36
+ # For Union types, check each union member
37
+ if is_union(field_type):
38
+ for arg in get_args(field_type):
39
+ result = _get_props_subclass(arg)
40
+ if result is not None:
41
+ return result
42
+
43
+ return None
44
+
45
+
46
+ def _find_props_in_list_annotation(field_type: Any) -> type | None:
47
+ """Find Props subclass within a list type annotation.
48
+
49
+ Args:
50
+ field_type: The type annotation to check (e.g., list[SomeProps] or list[SomeProps] | None).
51
+
52
+ Returns:
53
+ The Props subclass if found in a list annotation, None otherwise.
54
+ """
55
+ origin = get_origin(field_type)
56
+ if origin is list:
57
+ args = get_args(field_type)
58
+ if args:
59
+ return _get_props_subclass(args[0])
60
+
61
+ # Handle Union types - check if any union member is a list
62
+ if is_union(field_type):
63
+ for arg in get_args(field_type):
64
+ if arg is not type(None): # Skip None from Optional
65
+ list_element = _find_props_in_list_annotation(arg)
66
+ if list_element is not None:
67
+ return list_element
68
+
69
+ return None
70
+
71
+
72
+ class PropsField(BaseField[PROPS_FIELD_TYPE]):
73
+ """A field for a props class."""
74
+
75
+ def __init__(
76
+ self,
77
+ default: PROPS_FIELD_TYPE | _MISSING_TYPE = MISSING,
78
+ default_factory: Callable[[], PROPS_FIELD_TYPE] | None = None,
79
+ annotated_type: type[Any] | _MISSING_TYPE = MISSING,
80
+ ) -> None:
81
+ """Initialize the field.
82
+
83
+ Args:
84
+ default: The default value for the field.
85
+ default_factory: The default factory for the field.
86
+ annotated_type: The annotated type for the field.
87
+ """
88
+ super().__init__(default, default_factory, annotated_type)
89
+ self._name: str = "" # Will be set by metaclass
90
+
91
+ @property
92
+ def required(self) -> bool:
93
+ """Check if the field is required (for Pydantic compatibility).
94
+
95
+ Returns:
96
+ True if the field has no default value or factory.
97
+ """
98
+ return self.default is MISSING and self.default_factory is None
99
+
100
+ @property
101
+ def name(self) -> str | None:
102
+ """Field name (for Pydantic compatibility).
103
+
104
+ Note: This is set by the metaclass when processing fields.
105
+
106
+ Returns:
107
+ The field name if set, None otherwise.
108
+ """
109
+ return getattr(self, "_name", None)
110
+
111
+ def get_default(self) -> Any:
112
+ """Get the default value (for Pydantic compatibility).
113
+
114
+ Returns:
115
+ The default value for the field, or None if required.
116
+ """
117
+ try:
118
+ return self.default_value()
119
+ except ValueError:
120
+ # Field is required (no default)
121
+ return None
122
+
123
+ def __repr__(self) -> str:
124
+ """Represent the field in a readable format.
125
+
126
+ Returns:
127
+ The string representation of the field.
128
+ """
129
+ annotated_type_str = (
130
+ f", annotated_type={self.annotated_type!r}"
131
+ if self.annotated_type is not MISSING
132
+ else ""
133
+ )
134
+ if self.default is not MISSING:
135
+ return f"PropsField(default={self.default!r}{annotated_type_str})"
136
+ return (
137
+ f"PropsField(default_factory={self.default_factory!r}{annotated_type_str})"
138
+ )
139
+
140
+
141
+ def props_field(
142
+ default: PROPS_FIELD_TYPE | _MISSING_TYPE = MISSING,
143
+ default_factory: Callable[[], PROPS_FIELD_TYPE] | None = None,
144
+ ) -> PROPS_FIELD_TYPE:
145
+ """Create a field for a props class.
146
+
147
+ Args:
148
+ default: The default value for the field.
149
+ default_factory: The default factory for the field.
150
+
151
+ Returns:
152
+ The field for the props class.
153
+
154
+ Raises:
155
+ ValueError: If both default and default_factory are specified.
156
+ """
157
+ if default is not MISSING and default_factory is not None:
158
+ msg = "cannot specify both default and default_factory"
159
+ raise ValueError(msg)
160
+ return PropsField( # pyright: ignore [reportReturnType]
161
+ default=default,
162
+ default_factory=default_factory,
163
+ annotated_type=MISSING,
164
+ )
165
+
12
166
 
13
- class PropsBase(Base):
167
+ @dataclass_transform(field_specifiers=(props_field,))
168
+ class PropsBaseMeta(FieldBasedMeta):
169
+ """Meta class for PropsBase."""
170
+
171
+ @classmethod
172
+ def _process_annotated_fields(
173
+ cls,
174
+ namespace: dict[str, Any],
175
+ annotations: dict[str, Any],
176
+ inherited_fields: dict[str, PropsField],
177
+ ) -> dict[str, PropsField]:
178
+ own_fields: dict[str, PropsField] = {}
179
+
180
+ for key, annotation in annotations.items():
181
+ value = namespace.get(key, MISSING)
182
+
183
+ if value is MISSING:
184
+ # Field with only annotation, no default value
185
+ field = PropsField(annotated_type=annotation, default=None)
186
+ elif not isinstance(value, PropsField):
187
+ # Field with default value
188
+ field = PropsField(annotated_type=annotation, default=value)
189
+ else:
190
+ # Field is already a PropsField, update annotation
191
+ field = PropsField(
192
+ annotated_type=annotation,
193
+ default=value.default,
194
+ default_factory=value.default_factory,
195
+ )
196
+
197
+ own_fields[key] = field
198
+
199
+ return own_fields
200
+
201
+ @classmethod
202
+ def _create_field(
203
+ cls,
204
+ annotated_type: Any,
205
+ default: Any = MISSING,
206
+ default_factory: Callable[[], Any] | None = None,
207
+ ) -> PropsField:
208
+ return PropsField(
209
+ annotated_type=annotated_type,
210
+ default=default,
211
+ default_factory=default_factory,
212
+ )
213
+
214
+ @classmethod
215
+ def _finalize_fields(
216
+ cls,
217
+ namespace: dict[str, Any],
218
+ inherited_fields: dict[str, PropsField],
219
+ own_fields: dict[str, PropsField],
220
+ ) -> None:
221
+ # Call parent implementation
222
+ super()._finalize_fields(namespace, inherited_fields, own_fields)
223
+
224
+ # Add Pydantic compatibility
225
+ namespace["__fields__"] = namespace["_fields"]
226
+
227
+
228
+ class PropsBase(metaclass=PropsBaseMeta):
14
229
  """Base for a class containing props that can be serialized as a JS object."""
15
230
 
231
+ def __init__(self, **kwargs):
232
+ """Initialize the props with field values.
233
+
234
+ Args:
235
+ **kwargs: The field values to set.
236
+ """
237
+ # Set field values from kwargs with nested object instantiation
238
+ for key, value in kwargs.items():
239
+ field_info = self.get_fields().get(key)
240
+ if field_info:
241
+ field_type = field_info.annotated_type
242
+
243
+ # Check if this field expects a specific Props type and we got a dict
244
+ if isinstance(value, dict):
245
+ props_class = _get_props_subclass(field_type)
246
+ if props_class is not None:
247
+ value = props_class(**value)
248
+
249
+ # Check if this field expects a list of Props and we got a list of dicts
250
+ elif isinstance(value, list):
251
+ element_type = _find_props_in_list_annotation(field_type)
252
+ if element_type is not None:
253
+ # Convert each dict in the list to the appropriate Props class
254
+ value = [
255
+ element_type(**item) if isinstance(item, dict) else item
256
+ for item in value
257
+ ]
258
+
259
+ setattr(self, key, value)
260
+
261
+ # Set default values for fields not provided
262
+ for field_name, field in self.get_fields().items():
263
+ if field_name not in kwargs:
264
+ if field.default is not MISSING:
265
+ setattr(self, field_name, field.default)
266
+ elif field.default_factory is not None:
267
+ setattr(self, field_name, field.default_factory())
268
+ # Note: Fields with no default and no factory remain unset (required fields)
269
+
270
+ @classmethod
271
+ def get_fields(cls) -> dict[str, Any]:
272
+ """Get the fields of the object.
273
+
274
+ Returns:
275
+ The fields of the object.
276
+ """
277
+ return getattr(cls, "_fields", {})
278
+
16
279
  def json(self) -> str:
17
280
  """Convert the object to a json-like string.
18
281
 
@@ -27,31 +290,117 @@ class PropsBase(Base):
27
290
  {format.to_camel_case(key): value for key, value in self.dict().items()}
28
291
  ).json()
29
292
 
30
- def dict(self, *args, **kwargs):
293
+ def dict(
294
+ self,
295
+ exclude_none: bool = True,
296
+ include: set[str] | None = None,
297
+ exclude: set[str] | None = None,
298
+ **kwargs,
299
+ ):
31
300
  """Convert the object to a dictionary.
32
301
 
33
302
  Keys will be converted to camelCase.
34
303
  By default, None values are excluded (exclude_none=True).
35
304
 
36
305
  Args:
37
- *args: Arguments to pass to the parent class.
38
- **kwargs: Keyword arguments to pass to the parent class.
306
+ exclude_none: Whether to exclude None values.
307
+ include: Fields to include in the output.
308
+ exclude: Fields to exclude from the output.
309
+ **kwargs: Additional keyword arguments (for compatibility).
39
310
 
40
311
  Returns:
41
312
  The object as a dictionary.
42
313
  """
43
- kwargs.setdefault("exclude_none", True)
44
- return {
45
- format.to_camel_case(key): value
46
- for key, value in super().dict(*args, **kwargs).items()
47
- }
314
+ result = {}
48
315
 
316
+ for field_name in self.get_fields():
317
+ if hasattr(self, field_name):
318
+ value = getattr(self, field_name)
49
319
 
50
- class NoExtrasAllowedProps(Base):
320
+ # Apply include/exclude filters
321
+ if include is not None and field_name not in include:
322
+ continue
323
+ if exclude is not None and field_name in exclude:
324
+ continue
325
+
326
+ # Apply exclude_none logic
327
+ if exclude_none and value is None:
328
+ continue
329
+
330
+ # Recursively convert nested structures
331
+ value = self._convert_to_camel_case(
332
+ value, exclude_none, include, exclude
333
+ )
334
+
335
+ # Convert key to camelCase
336
+ camel_key = format.to_camel_case(field_name)
337
+ result[camel_key] = value
338
+
339
+ return result
340
+
341
+ def _convert_to_camel_case(
342
+ self,
343
+ value: Any,
344
+ exclude_none: bool = True,
345
+ include: set[str] | None = None,
346
+ exclude: set[str] | None = None,
347
+ ) -> Any:
348
+ """Recursively convert nested dictionaries and lists to camelCase.
349
+
350
+ Args:
351
+ value: The value to convert.
352
+ exclude_none: Whether to exclude None values.
353
+ include: Fields to include in the output.
354
+ exclude: Fields to exclude from the output.
355
+
356
+ Returns:
357
+ The converted value with camelCase keys.
358
+ """
359
+ if isinstance(value, PropsBase):
360
+ # Convert nested PropsBase objects
361
+ return value.dict(
362
+ exclude_none=exclude_none, include=include, exclude=exclude
363
+ )
364
+ if isinstance(value, dict):
365
+ # Convert dictionary keys to camelCase
366
+ return {
367
+ format.to_camel_case(k): self._convert_to_camel_case(
368
+ v, exclude_none, include, exclude
369
+ )
370
+ for k, v in value.items()
371
+ if not (exclude_none and v is None)
372
+ }
373
+ if isinstance(value, (list, tuple)):
374
+ # Convert list/tuple items recursively
375
+ return [
376
+ self._convert_to_camel_case(item, exclude_none, include, exclude)
377
+ for item in value
378
+ ]
379
+ # Return primitive values as-is
380
+ return value
381
+
382
+
383
+ @serializer(to=dict)
384
+ def serialize_props_base(value: PropsBase) -> dict:
385
+ """Serialize a PropsBase instance.
386
+
387
+ Unlike serialize_base, this preserves callables (lambdas) since they're
388
+ needed for AG Grid and other components that process them on the frontend.
389
+
390
+ Args:
391
+ value: The PropsBase instance to serialize.
392
+
393
+ Returns:
394
+ Dictionary representation of the PropsBase instance.
395
+ """
396
+ return value.dict()
397
+
398
+
399
+ class NoExtrasAllowedProps(PropsBase):
51
400
  """A class that holds props to be passed or applied to a component with no extra props allowed."""
52
401
 
53
402
  def __init__(self, component_name: str | None = None, **kwargs):
54
- """Initialize the props.
403
+ """Initialize the props with validation.
55
404
 
56
405
  Args:
57
406
  component_name: The custom name of the component.
@@ -61,17 +410,17 @@ class NoExtrasAllowedProps(Base):
61
410
  InvalidPropValueError: If invalid props are passed on instantiation.
62
411
  """
63
412
  component_name = component_name or type(self).__name__
64
- try:
65
- super().__init__(**kwargs)
66
- except ValidationError as e:
67
- invalid_fields = ", ".join([error["loc"][0] for error in e.errors()]) # pyright: ignore [reportCallIssue, reportArgumentType]
68
- supported_props_str = ", ".join(f'"{field}"' for field in self.get_fields())
69
- msg = f"Invalid prop(s) {invalid_fields} for {component_name!r}. Supported props are {supported_props_str}"
70
- raise InvalidPropValueError(msg) from None
71
-
72
- class Config: # pyright: ignore [reportIncompatibleVariableOverride]
73
- """Pydantic config."""
74
-
75
- arbitrary_types_allowed = True
76
- use_enum_values = True
77
- extra = "forbid"
413
+
414
+ # Validate fields BEFORE setting them
415
+ known_fields = set(self.__class__.get_fields().keys())
416
+ provided_fields = set(kwargs.keys())
417
+ invalid_fields = provided_fields - known_fields
418
+
419
+ if invalid_fields:
420
+ invalid_fields_str = ", ".join(invalid_fields)
421
+ supported_props_str = ", ".join(f'"{field}"' for field in known_fields)
422
+ msg = f"Invalid prop(s) {invalid_fields_str} for {component_name!r}. Supported props are {supported_props_str}"
423
+ raise InvalidPropValueError(msg)
424
+
425
+ # Use parent class initialization after validation
426
+ super().__init__(**kwargs)
@@ -334,7 +334,11 @@ class AccordionHeader(AccordionComponent):
334
334
  Returns:
335
335
  The style of the component.
336
336
  """
337
- return {"display": "flex"}
337
+ return {
338
+ "display": "flex",
339
+ # Reset some values to ensure consistent styling without tailwind reset.
340
+ "margin": "0",
341
+ }
338
342
 
339
343
 
340
344
  class AccordionTrigger(AccordionComponent):
@@ -399,6 +403,9 @@ class AccordionTrigger(AccordionComponent):
399
403
  "color": "var(--accent-contrast)",
400
404
  },
401
405
  },
406
+ # Reset some values to ensure consistent styling without tailwind reset.
407
+ "background": "none",
408
+ "border": "none",
402
409
  }
403
410
 
404
411
 
@@ -10,7 +10,7 @@ from reflex.components.component import Component, ComponentNamespace
10
10
  from reflex.components.core.breakpoints import Breakpoints
11
11
  from reflex.components.lucide.icon import Icon
12
12
  from reflex.components.radix.primitives.base import RadixPrimitiveComponent
13
- from reflex.event import EventType
13
+ from reflex.event import EventType, PointerEventInfo
14
14
  from reflex.vars.base import Var
15
15
 
16
16
  LiteralAccordionType = Literal["single", "multiple"]
@@ -102,9 +102,9 @@ class AccordionComponent(RadixPrimitiveComponent):
102
102
  autofocus: bool | None = None,
103
103
  custom_attrs: dict[str, Var | Any] | None = None,
104
104
  on_blur: EventType[()] | None = None,
105
- on_click: EventType[()] | None = None,
106
- on_context_menu: EventType[()] | None = None,
107
- on_double_click: EventType[()] | None = None,
105
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
106
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
107
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
108
108
  on_focus: EventType[()] | None = None,
109
109
  on_mount: EventType[()] | None = None,
110
110
  on_mouse_down: EventType[()] | None = None,
@@ -115,6 +115,7 @@ class AccordionComponent(RadixPrimitiveComponent):
115
115
  on_mouse_over: EventType[()] | None = None,
116
116
  on_mouse_up: EventType[()] | None = None,
117
117
  on_scroll: EventType[()] | None = None,
118
+ on_scroll_end: EventType[()] | None = None,
118
119
  on_unmount: EventType[()] | None = None,
119
120
  **props,
120
121
  ) -> AccordionComponent:
@@ -239,9 +240,9 @@ class AccordionRoot(AccordionComponent):
239
240
  autofocus: bool | None = None,
240
241
  custom_attrs: dict[str, Var | Any] | None = None,
241
242
  on_blur: EventType[()] | None = None,
242
- on_click: EventType[()] | None = None,
243
- on_context_menu: EventType[()] | None = None,
244
- on_double_click: EventType[()] | None = None,
243
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
244
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
245
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
245
246
  on_focus: EventType[()] | None = None,
246
247
  on_mount: EventType[()] | None = None,
247
248
  on_mouse_down: EventType[()] | None = None,
@@ -252,6 +253,7 @@ class AccordionRoot(AccordionComponent):
252
253
  on_mouse_over: EventType[()] | None = None,
253
254
  on_mouse_up: EventType[()] | None = None,
254
255
  on_scroll: EventType[()] | None = None,
256
+ on_scroll_end: EventType[()] | None = None,
255
257
  on_unmount: EventType[()] | None = None,
256
258
  on_value_change: EventType[()] | EventType[str | list[str]] | None = None,
257
259
  **props,
@@ -373,9 +375,9 @@ class AccordionItem(AccordionComponent):
373
375
  autofocus: bool | None = None,
374
376
  custom_attrs: dict[str, Var | Any] | None = None,
375
377
  on_blur: EventType[()] | None = None,
376
- on_click: EventType[()] | None = None,
377
- on_context_menu: EventType[()] | None = None,
378
- on_double_click: EventType[()] | None = None,
378
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
379
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
380
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
379
381
  on_focus: EventType[()] | None = None,
380
382
  on_mount: EventType[()] | None = None,
381
383
  on_mouse_down: EventType[()] | None = None,
@@ -386,6 +388,7 @@ class AccordionItem(AccordionComponent):
386
388
  on_mouse_over: EventType[()] | None = None,
387
389
  on_mouse_up: EventType[()] | None = None,
388
390
  on_scroll: EventType[()] | None = None,
391
+ on_scroll_end: EventType[()] | None = None,
389
392
  on_unmount: EventType[()] | None = None,
390
393
  **props,
391
394
  ) -> AccordionItem:
@@ -496,9 +499,9 @@ class AccordionHeader(AccordionComponent):
496
499
  autofocus: bool | None = None,
497
500
  custom_attrs: dict[str, Var | Any] | None = None,
498
501
  on_blur: EventType[()] | None = None,
499
- on_click: EventType[()] | None = None,
500
- on_context_menu: EventType[()] | None = None,
501
- on_double_click: EventType[()] | None = None,
502
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
503
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
504
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
502
505
  on_focus: EventType[()] | None = None,
503
506
  on_mount: EventType[()] | None = None,
504
507
  on_mouse_down: EventType[()] | None = None,
@@ -509,6 +512,7 @@ class AccordionHeader(AccordionComponent):
509
512
  on_mouse_over: EventType[()] | None = None,
510
513
  on_mouse_up: EventType[()] | None = None,
511
514
  on_scroll: EventType[()] | None = None,
515
+ on_scroll_end: EventType[()] | None = None,
512
516
  on_unmount: EventType[()] | None = None,
513
517
  **props,
514
518
  ) -> AccordionHeader:
@@ -615,9 +619,9 @@ class AccordionTrigger(AccordionComponent):
615
619
  autofocus: bool | None = None,
616
620
  custom_attrs: dict[str, Var | Any] | None = None,
617
621
  on_blur: EventType[()] | None = None,
618
- on_click: EventType[()] | None = None,
619
- on_context_menu: EventType[()] | None = None,
620
- on_double_click: EventType[()] | None = None,
622
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
623
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
624
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
621
625
  on_focus: EventType[()] | None = None,
622
626
  on_mount: EventType[()] | None = None,
623
627
  on_mouse_down: EventType[()] | None = None,
@@ -628,6 +632,7 @@ class AccordionTrigger(AccordionComponent):
628
632
  on_mouse_over: EventType[()] | None = None,
629
633
  on_mouse_up: EventType[()] | None = None,
630
634
  on_scroll: EventType[()] | None = None,
635
+ on_scroll_end: EventType[()] | None = None,
631
636
  on_unmount: EventType[()] | None = None,
632
637
  **props,
633
638
  ) -> AccordionTrigger:
@@ -672,9 +677,9 @@ class AccordionIcon(Icon):
672
677
  autofocus: bool | None = None,
673
678
  custom_attrs: dict[str, Var | Any] | None = None,
674
679
  on_blur: EventType[()] | None = None,
675
- on_click: EventType[()] | None = None,
676
- on_context_menu: EventType[()] | None = None,
677
- on_double_click: EventType[()] | None = None,
680
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
681
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
682
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
678
683
  on_focus: EventType[()] | None = None,
679
684
  on_mount: EventType[()] | None = None,
680
685
  on_mouse_down: EventType[()] | None = None,
@@ -685,6 +690,7 @@ class AccordionIcon(Icon):
685
690
  on_mouse_over: EventType[()] | None = None,
686
691
  on_mouse_up: EventType[()] | None = None,
687
692
  on_scroll: EventType[()] | None = None,
693
+ on_scroll_end: EventType[()] | None = None,
688
694
  on_unmount: EventType[()] | None = None,
689
695
  **props,
690
696
  ) -> AccordionIcon:
@@ -788,9 +794,9 @@ class AccordionContent(AccordionComponent):
788
794
  autofocus: bool | None = None,
789
795
  custom_attrs: dict[str, Var | Any] | None = None,
790
796
  on_blur: EventType[()] | None = None,
791
- on_click: EventType[()] | None = None,
792
- on_context_menu: EventType[()] | None = None,
793
- on_double_click: EventType[()] | None = None,
797
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
798
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
799
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
794
800
  on_focus: EventType[()] | None = None,
795
801
  on_mount: EventType[()] | None = None,
796
802
  on_mouse_down: EventType[()] | None = None,
@@ -801,6 +807,7 @@ class AccordionContent(AccordionComponent):
801
807
  on_mouse_over: EventType[()] | None = None,
802
808
  on_mouse_up: EventType[()] | None = None,
803
809
  on_scroll: EventType[()] | None = None,
810
+ on_scroll_end: EventType[()] | None = None,
804
811
  on_unmount: EventType[()] | None = None,
805
812
  **props,
806
813
  ) -> AccordionContent:
@@ -8,7 +8,7 @@ from typing import Any, overload
8
8
 
9
9
  from reflex.components.component import Component
10
10
  from reflex.components.core.breakpoints import Breakpoints
11
- from reflex.event import EventType
11
+ from reflex.event import EventType, PointerEventInfo
12
12
  from reflex.vars.base import Var
13
13
 
14
14
  class RadixPrimitiveComponent(Component):
@@ -30,9 +30,9 @@ class RadixPrimitiveComponent(Component):
30
30
  autofocus: bool | None = None,
31
31
  custom_attrs: dict[str, Var | Any] | None = None,
32
32
  on_blur: EventType[()] | None = None,
33
- on_click: EventType[()] | None = None,
34
- on_context_menu: EventType[()] | None = None,
35
- on_double_click: EventType[()] | None = None,
33
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
34
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
35
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
36
36
  on_focus: EventType[()] | None = None,
37
37
  on_mount: EventType[()] | None = None,
38
38
  on_mouse_down: EventType[()] | None = None,
@@ -43,6 +43,7 @@ class RadixPrimitiveComponent(Component):
43
43
  on_mouse_over: EventType[()] | None = None,
44
44
  on_mouse_up: EventType[()] | None = None,
45
45
  on_scroll: EventType[()] | None = None,
46
+ on_scroll_end: EventType[()] | None = None,
46
47
  on_unmount: EventType[()] | None = None,
47
48
  **props,
48
49
  ) -> RadixPrimitiveComponent:
@@ -83,9 +84,9 @@ class RadixPrimitiveComponentWithClassName(RadixPrimitiveComponent):
83
84
  autofocus: bool | None = None,
84
85
  custom_attrs: dict[str, Var | Any] | None = None,
85
86
  on_blur: EventType[()] | None = None,
86
- on_click: EventType[()] | None = None,
87
- on_context_menu: EventType[()] | None = None,
88
- on_double_click: EventType[()] | None = None,
87
+ on_click: EventType[()] | EventType[PointerEventInfo] | None = None,
88
+ on_context_menu: EventType[()] | EventType[PointerEventInfo] | None = None,
89
+ on_double_click: EventType[()] | EventType[PointerEventInfo] | None = None,
89
90
  on_focus: EventType[()] | None = None,
90
91
  on_mount: EventType[()] | None = None,
91
92
  on_mouse_down: EventType[()] | None = None,
@@ -96,6 +97,7 @@ class RadixPrimitiveComponentWithClassName(RadixPrimitiveComponent):
96
97
  on_mouse_over: EventType[()] | None = None,
97
98
  on_mouse_up: EventType[()] | None = None,
98
99
  on_scroll: EventType[()] | None = None,
100
+ on_scroll_end: EventType[()] | None = None,
99
101
  on_unmount: EventType[()] | None = None,
100
102
  **props,
101
103
  ) -> RadixPrimitiveComponentWithClassName: