reflex 0.8.0a3__py3-none-any.whl → 0.8.0a5__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 (35) hide show
  1. reflex/.templates/jinja/web/styles/styles.css.jinja2 +1 -0
  2. reflex/.templates/web/app/routes.js +3 -3
  3. reflex/.templates/web/styles/__reflex_style_reset.css +399 -0
  4. reflex/.templates/web/utils/client_side_routing.js +1 -1
  5. reflex/.templates/web/utils/state.js +32 -21
  6. reflex/.templates/web/vite.config.js +6 -0
  7. reflex/app.py +50 -46
  8. reflex/compiler/compiler.py +26 -10
  9. reflex/compiler/utils.py +4 -2
  10. reflex/components/base/meta.py +4 -15
  11. reflex/components/core/foreach.py +2 -2
  12. reflex/components/core/match.py +3 -3
  13. reflex/components/core/upload.py +2 -1
  14. reflex/components/datadisplay/code.py +12 -7
  15. reflex/components/datadisplay/shiki_code_block.py +5 -3
  16. reflex/components/markdown/markdown.py +5 -3
  17. reflex/components/plotly/plotly.py +12 -6
  18. reflex/components/radix/themes/color_mode.py +5 -6
  19. reflex/components/recharts/cartesian.py +9 -2
  20. reflex/constants/compiler.py +7 -0
  21. reflex/constants/route.py +13 -6
  22. reflex/environment.py +6 -4
  23. reflex/route.py +159 -71
  24. reflex/state.py +21 -4
  25. reflex/utils/exec.py +10 -10
  26. reflex/utils/format.py +1 -5
  27. reflex/utils/misc.py +40 -0
  28. reflex/utils/prerequisites.py +6 -11
  29. reflex/utils/pyi_generator.py +23 -40
  30. reflex/utils/types.py +1 -1
  31. {reflex-0.8.0a3.dist-info → reflex-0.8.0a5.dist-info}/METADATA +2 -2
  32. {reflex-0.8.0a3.dist-info → reflex-0.8.0a5.dist-info}/RECORD +35 -34
  33. {reflex-0.8.0a3.dist-info → reflex-0.8.0a5.dist-info}/WHEEL +0 -0
  34. {reflex-0.8.0a3.dist-info → reflex-0.8.0a5.dist-info}/entry_points.txt +0 -0
  35. {reflex-0.8.0a3.dist-info → reflex-0.8.0a5.dist-info}/licenses/LICENSE +0 -0
reflex/app.py CHANGED
@@ -77,6 +77,7 @@ from reflex.event import (
77
77
  EventType,
78
78
  IndividualEventType,
79
79
  get_hydrate_event,
80
+ noop,
80
81
  )
81
82
  from reflex.model import Model, get_db_status
82
83
  from reflex.page import DECORATED_PAGES
@@ -213,17 +214,21 @@ def default_overlay_component() -> Component:
213
214
  return Fragment.create(memo(default_overlay_components)())
214
215
 
215
216
 
216
- def default_error_boundary(*children: Component) -> Component:
217
+ def default_error_boundary(*children: Component, **props) -> Component:
217
218
  """Default error_boundary attribute for App.
218
219
 
219
220
  Args:
220
221
  *children: The children to render in the error boundary.
222
+ **props: The props to pass to the error boundary.
221
223
 
222
224
  Returns:
223
225
  The default error_boundary, which is an ErrorBoundary.
224
226
 
225
227
  """
226
- return ErrorBoundary.create(*children)
228
+ return ErrorBoundary.create(
229
+ *children,
230
+ **props,
231
+ )
227
232
 
228
233
 
229
234
  class OverlayFragment(Fragment):
@@ -332,6 +337,9 @@ class App(MiddlewareMixin, LifespanMixin):
332
337
  # A list of URLs to [stylesheets](https://reflex.dev/docs/styling/custom-stylesheets/) to include in the app.
333
338
  stylesheets: list[str] = dataclasses.field(default_factory=list)
334
339
 
340
+ # Whether to include CSS reset for margin and padding (defaults to True).
341
+ reset_style: bool = dataclasses.field(default=True)
342
+
335
343
  # A component that is present on every page (defaults to the Connection Error banner).
336
344
  overlay_component: Component | ComponentCallable | None = dataclasses.field(
337
345
  default=None
@@ -342,7 +350,9 @@ class App(MiddlewareMixin, LifespanMixin):
342
350
  dataclasses.field(
343
351
  default_factory=lambda: {
344
352
  (55, "ErrorBoundary"): (
345
- lambda stateful: default_error_boundary() if stateful else None
353
+ lambda stateful: default_error_boundary(
354
+ **({"on_error": noop()} if not stateful else {})
355
+ )
346
356
  ),
347
357
  (5, "Overlay"): (
348
358
  lambda stateful: default_overlay_component() if stateful else None
@@ -755,9 +765,9 @@ class App(MiddlewareMixin, LifespanMixin):
755
765
  msg = "Route must be set if component is not a callable."
756
766
  raise exceptions.RouteValueError(msg)
757
767
  # Format the route.
758
- route = format.format_route(component.__name__)
768
+ route = format.format_route(format.to_kebab_case(component.__name__))
759
769
  else:
760
- route = format.format_route(route, format_case=False)
770
+ route = format.format_route(route)
761
771
 
762
772
  if route == constants.Page404.SLUG:
763
773
  if component is None:
@@ -812,10 +822,11 @@ class App(MiddlewareMixin, LifespanMixin):
812
822
  state = self._state if self._state else State
813
823
  state.setup_dynamic_args(get_route_args(route))
814
824
 
815
- if on_load:
816
- self._load_events[route] = (
817
- on_load if isinstance(on_load, list) else [on_load]
818
- )
825
+ self._load_events[route] = (
826
+ (on_load if isinstance(on_load, list) else [on_load])
827
+ if on_load is not None
828
+ else []
829
+ )
819
830
 
820
831
  self._unevaluated_pages[route] = unevaluated_page
821
832
 
@@ -844,48 +855,35 @@ class App(MiddlewareMixin, LifespanMixin):
844
855
  if save_page:
845
856
  self._pages[route] = component
846
857
 
847
- def get_load_events(self, route: str) -> list[IndividualEventType[()]]:
858
+ @functools.cached_property
859
+ def router(self) -> Callable[[str], str | None]:
860
+ """Get the route computer function.
861
+
862
+ Returns:
863
+ The route computer function.
864
+ """
865
+ from reflex.route import get_router
866
+
867
+ return get_router(list(self._pages))
868
+
869
+ def get_load_events(self, path: str) -> list[IndividualEventType[()]]:
848
870
  """Get the load events for a route.
849
871
 
850
872
  Args:
851
- route: The route to get the load events for.
873
+ path: The route to get the load events for.
852
874
 
853
875
  Returns:
854
876
  The load events for the route.
855
877
  """
856
- route = route.lstrip("/").rstrip("/")
857
- if route == "":
858
- return self._load_events.get(constants.PageNames.INDEX_ROUTE, [])
859
-
860
- # Separate the pages by route type.
861
- static_page_paths_to_page_route = {}
862
- dynamic_page_paths_to_page_route = {}
863
- for page_route in list(self._pages) + list(self._unevaluated_pages):
864
- page_path = page_route.lstrip("/").rstrip("/")
865
- if "[" in page_path and "]" in page_path:
866
- dynamic_page_paths_to_page_route[page_path] = page_route
867
- else:
868
- static_page_paths_to_page_route[page_path] = page_route
869
-
870
- # Check for static routes.
871
- if (page_route := static_page_paths_to_page_route.get(route)) is not None:
872
- return self._load_events.get(page_route, [])
873
-
874
- # Check for dynamic routes.
875
- parts = route.split("/")
876
- for page_path, page_route in dynamic_page_paths_to_page_route.items():
877
- page_parts = page_path.split("/")
878
- if len(page_parts) != len(parts):
879
- continue
880
- if all(
881
- part == page_part
882
- or (page_part.startswith("[") and page_part.endswith("]"))
883
- for part, page_part in zip(parts, page_parts, strict=False)
884
- ):
885
- return self._load_events.get(page_route, [])
886
-
887
- # Default to 404 page load events if no match found.
888
- return self._load_events.get("404", [])
878
+ four_oh_four_load_events = self._load_events.get("404", [])
879
+ route = self.router(path)
880
+ if not route:
881
+ # If the path is not a valid route, return the 404 page load events.
882
+ return four_oh_four_load_events
883
+ return self._load_events.get(
884
+ route,
885
+ four_oh_four_load_events,
886
+ )
889
887
 
890
888
  def _check_routes_conflict(self, new_route: str):
891
889
  """Verify if there is any conflict between the new route and any existing route.
@@ -906,7 +904,6 @@ class App(MiddlewareMixin, LifespanMixin):
906
904
  segments = (
907
905
  constants.RouteRegex.SINGLE_SEGMENT,
908
906
  constants.RouteRegex.DOUBLE_SEGMENT,
909
- constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
910
907
  constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
911
908
  )
912
909
  for route in self._pages:
@@ -1358,7 +1355,9 @@ class App(MiddlewareMixin, LifespanMixin):
1358
1355
  )
1359
1356
 
1360
1357
  # Compile the root stylesheet with base styles.
1361
- _submit_work(compiler.compile_root_stylesheet, self.stylesheets)
1358
+ _submit_work(
1359
+ compiler.compile_root_stylesheet, self.stylesheets, self.reset_style
1360
+ )
1362
1361
 
1363
1362
  # Compile the theme.
1364
1363
  _submit_work(compile_theme, self.style)
@@ -1730,6 +1729,11 @@ async def process(
1730
1729
  # assignment will recurse into substates and force recalculation of
1731
1730
  # dependent ComputedVar (dynamic route variables)
1732
1731
  state.router_data = router_data
1732
+ router_data[constants.RouteVar.PATH] = "/" + (
1733
+ app.router(path) or "404"
1734
+ if (path := router_data.get(constants.RouteVar.PATH))
1735
+ else "404"
1736
+ ).removeprefix("/")
1733
1737
  state.router = RouterData(router_data)
1734
1738
 
1735
1739
  # Preprocess the event.
@@ -19,7 +19,7 @@ from reflex.components.component import (
19
19
  StatefulComponent,
20
20
  )
21
21
  from reflex.config import get_config
22
- from reflex.constants.compiler import PageNames
22
+ from reflex.constants.compiler import PageNames, ResetStylesheet
23
23
  from reflex.constants.state import FIELD_MARKER
24
24
  from reflex.environment import environment
25
25
  from reflex.state import BaseState
@@ -86,6 +86,8 @@ def _compile_app(app_root: Component) -> str:
86
86
  (_normalize_library_name(name), name) for name in bundled_libraries
87
87
  ]
88
88
 
89
+ window_libraries_deduped = list(dict.fromkeys(window_libraries))
90
+
89
91
  app_root_imports = app_root._get_all_imports()
90
92
  _apply_common_imports(app_root_imports)
91
93
 
@@ -93,7 +95,7 @@ def _compile_app(app_root: Component) -> str:
93
95
  imports=utils.compile_imports(app_root_imports),
94
96
  custom_codes=app_root._get_all_custom_code(),
95
97
  hooks=app_root._get_all_hooks(),
96
- window_libraries=window_libraries,
98
+ window_libraries=window_libraries_deduped,
97
99
  render=app_root.render(),
98
100
  dynamic_imports=app_root._get_all_dynamic_imports(),
99
101
  )
@@ -171,18 +173,21 @@ def _compile_page(
171
173
  )
172
174
 
173
175
 
174
- def compile_root_stylesheet(stylesheets: list[str]) -> tuple[str, str]:
176
+ def compile_root_stylesheet(
177
+ stylesheets: list[str], reset_style: bool = True
178
+ ) -> tuple[str, str]:
175
179
  """Compile the root stylesheet.
176
180
 
177
181
  Args:
178
182
  stylesheets: The stylesheets to include in the root stylesheet.
183
+ reset_style: Whether to include CSS reset for margin and padding.
179
184
 
180
185
  Returns:
181
186
  The path and code of the compiled root stylesheet.
182
187
  """
183
188
  output_path = utils.get_root_stylesheet_path()
184
189
 
185
- code = _compile_root_stylesheet(stylesheets)
190
+ code = _compile_root_stylesheet(stylesheets, reset_style)
186
191
 
187
192
  return output_path, code
188
193
 
@@ -224,11 +229,12 @@ def _validate_stylesheet(stylesheet_full_path: Path, assets_app_path: Path) -> N
224
229
  RADIX_THEMES_STYLESHEET = "@radix-ui/themes/styles.css"
225
230
 
226
231
 
227
- def _compile_root_stylesheet(stylesheets: list[str]) -> str:
232
+ def _compile_root_stylesheet(stylesheets: list[str], reset_style: bool = True) -> str:
228
233
  """Compile the root stylesheet.
229
234
 
230
235
  Args:
231
236
  stylesheets: The stylesheets to include in the root stylesheet.
237
+ reset_style: Whether to include CSS reset for margin and padding.
232
238
 
233
239
  Returns:
234
240
  The compiled root stylesheet.
@@ -237,11 +243,21 @@ def _compile_root_stylesheet(stylesheets: list[str]) -> str:
237
243
  FileNotFoundError: If a specified stylesheet in assets directory does not exist.
238
244
  """
239
245
  # Add stylesheets from plugins.
240
- sheets = [RADIX_THEMES_STYLESHEET] + [
241
- sheet
242
- for plugin in get_config().plugins
243
- for sheet in plugin.get_stylesheet_paths()
244
- ]
246
+ sheets = []
247
+
248
+ # Add CSS reset if enabled
249
+ if reset_style:
250
+ # Reference the vendored style reset file (automatically copied from .templates/web)
251
+ sheets.append(f"./{ResetStylesheet.FILENAME}")
252
+
253
+ sheets.extend(
254
+ [RADIX_THEMES_STYLESHEET]
255
+ + [
256
+ sheet
257
+ for plugin in get_config().plugins
258
+ for sheet in plugin.get_stylesheet_paths()
259
+ ]
260
+ )
245
261
 
246
262
  failed_to_import_sass = False
247
263
  assets_app_path = Path.cwd() / constants.Dirs.APP_ASSETS
reflex/compiler/utils.py CHANGED
@@ -417,9 +417,11 @@ def _format_route_part(part: str) -> str:
417
417
  if part.startswith("[") and part.endswith("]"):
418
418
  if part.startswith(("[...", "[[...")):
419
419
  return "$"
420
+ if part.startswith("[["):
421
+ return "($" + part.removeprefix("[[").removesuffix("]]") + ")"
420
422
  # We don't add [] here since we are reusing them from the input
421
- return "$" + part + "_"
422
- return "[" + part + "]_"
423
+ return "$" + part
424
+ return "[" + part + "]"
423
425
 
424
426
 
425
427
  def _path_to_file_stem(path: str) -> str:
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from reflex.components.base.bare import Bare
6
6
  from reflex.components.el import elements
7
+ from reflex.vars.base import Var
7
8
 
8
9
 
9
10
  class Title(elements.Title):
@@ -28,31 +29,19 @@ class Title(elements.Title):
28
29
  class Meta(elements.Meta):
29
30
  """A component that displays metadata for the current page."""
30
31
 
31
- # The description of character encoding.
32
- char_set: str | None = None
33
-
34
- # The value of meta.
35
- content: str | None = None
36
-
37
- # The name of metadata.
38
- name: str | None = None
39
-
40
- # The type of metadata value.
41
- property: str | None = None
42
-
43
32
  # The type of metadata value.
44
- http_equiv: str | None = None
33
+ property: Var[str]
45
34
 
46
35
 
47
36
  class Description(elements.Meta):
48
37
  """A component that displays the title of the current page."""
49
38
 
50
39
  # The type of the description.
51
- name: str | None = "description"
40
+ name: Var[str] = Var.create("description")
52
41
 
53
42
 
54
43
  class Image(elements.Meta):
55
44
  """A component that displays the title of the current page."""
56
45
 
57
46
  # The type of the image.
58
- property: str | None = "og:image"
47
+ property: Var[str] = Var.create("og:image")
@@ -8,7 +8,7 @@ from collections.abc import Callable, Iterable
8
8
  from typing import Any
9
9
 
10
10
  from reflex.components.base.fragment import Fragment
11
- from reflex.components.component import Component
11
+ from reflex.components.component import Component, field
12
12
  from reflex.components.core.cond import cond
13
13
  from reflex.components.tags import IterTag
14
14
  from reflex.constants import MemoizationMode
@@ -36,7 +36,7 @@ class Foreach(Component):
36
36
  iterable: Var[Iterable]
37
37
 
38
38
  # A function from the render args to the component.
39
- render_fn: Callable = Fragment.create
39
+ render_fn: Callable = field(default=Fragment.create, is_javascript_property=False)
40
40
 
41
41
  @classmethod
42
42
  def create(
@@ -4,7 +4,7 @@ import textwrap
4
4
  from typing import Any
5
5
 
6
6
  from reflex.components.base import Fragment
7
- from reflex.components.component import BaseComponent, Component, MemoizationLeaf
7
+ from reflex.components.component import BaseComponent, Component, MemoizationLeaf, field
8
8
  from reflex.components.tags import MatchTag, Tag
9
9
  from reflex.style import Style
10
10
  from reflex.utils import format
@@ -21,10 +21,10 @@ class Match(MemoizationLeaf):
21
21
  cond: Var[Any]
22
22
 
23
23
  # The list of match cases to be matched.
24
- match_cases: list[Any] = []
24
+ match_cases: list[Any] = field(default_factory=list, is_javascript_property=False)
25
25
 
26
26
  # The catchall case to match.
27
- default: Any
27
+ default: Any = field(default=None, is_javascript_property=False)
28
28
 
29
29
  @classmethod
30
30
  def create(cls, cond: Any, *cases) -> Component | Var:
@@ -12,6 +12,7 @@ from reflex.components.component import (
12
12
  ComponentNamespace,
13
13
  MemoizationLeaf,
14
14
  StatefulComponent,
15
+ field,
15
16
  )
16
17
  from reflex.components.core.cond import cond
17
18
  from reflex.components.el.elements.forms import Input
@@ -234,7 +235,7 @@ class Upload(MemoizationLeaf):
234
235
  on_drop: EventHandler[_on_drop_spec]
235
236
 
236
237
  # Style rules to apply when actively dragging.
237
- drag_active_style: Style | None = None
238
+ drag_active_style: Style | None = field(default=None, is_javascript_property=False)
238
239
 
239
240
  @classmethod
240
241
  def create(cls, *children, **props) -> Component:
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import dataclasses
6
6
  from typing import ClassVar, Literal
7
7
 
8
- from reflex.components.component import Component, ComponentNamespace
8
+ from reflex.components.component import Component, ComponentNamespace, field
9
9
  from reflex.components.core.cond import color_mode_cond
10
10
  from reflex.components.lucide.icon import Icon
11
11
  from reflex.components.markdown.markdown import MarkdownComponentMap
@@ -407,16 +407,24 @@ class CodeBlock(Component, MarkdownComponentMap):
407
407
  wrap_long_lines: Var[bool]
408
408
 
409
409
  # A custom style for the code block.
410
- custom_style: dict[str, str | Var | Color] = {}
410
+ custom_style: dict[str, str | Var | Color] = field(
411
+ default_factory=dict, is_javascript_property=False
412
+ )
411
413
 
412
414
  # Props passed down to the code tag.
413
415
  code_tag_props: Var[dict[str, str]]
414
416
 
415
417
  # Whether a copy button should appear.
416
- can_copy: bool | None = False
418
+ can_copy: bool | None = field(
419
+ default=False,
420
+ is_javascript_property=False,
421
+ )
417
422
 
418
423
  # A custom copy button to override the default one.
419
- copy_button: bool | Component | None = None
424
+ copy_button: bool | Component | None = field(
425
+ default=None,
426
+ is_javascript_property=False,
427
+ )
420
428
 
421
429
  @classmethod
422
430
  def create(
@@ -498,9 +506,6 @@ class CodeBlock(Component, MarkdownComponentMap):
498
506
 
499
507
  return out
500
508
 
501
- def _exclude_props(self) -> list[str]:
502
- return ["can_copy", "copy_button"]
503
-
504
509
 
505
510
  class CodeblockNamespace(ComponentNamespace):
506
511
  """Namespace for the CodeBlock component."""
@@ -8,7 +8,7 @@ from collections import defaultdict
8
8
  from dataclasses import dataclass
9
9
  from typing import Any, Literal
10
10
 
11
- from reflex.components.component import Component, ComponentNamespace
11
+ from reflex.components.component import Component, ComponentNamespace, field
12
12
  from reflex.components.core.colors import color
13
13
  from reflex.components.core.cond import color_mode_cond
14
14
  from reflex.components.el.elements.forms import Button
@@ -721,10 +721,12 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
721
721
  show_line_numbers: Var[bool]
722
722
 
723
723
  # Whether a copy button should appear.
724
- can_copy: bool = False
724
+ can_copy: bool = field(default=False, is_javascript_property=False)
725
725
 
726
726
  # copy_button: A custom copy button to override the default one.
727
- copy_button: Component | bool | None = None
727
+ copy_button: Component | bool | None = field(
728
+ default=None, is_javascript_property=False
729
+ )
728
730
 
729
731
  @classmethod
730
732
  def create(
@@ -9,7 +9,7 @@ from functools import lru_cache
9
9
  from hashlib import md5
10
10
  from typing import Any
11
11
 
12
- from reflex.components.component import BaseComponent, Component, CustomComponent
12
+ from reflex.components.component import BaseComponent, Component, CustomComponent, field
13
13
  from reflex.components.tags.tag import Tag
14
14
  from reflex.utils.imports import ImportDict, ImportVar
15
15
  from reflex.vars.base import LiteralVar, Var, VarData
@@ -150,10 +150,12 @@ class Markdown(Component):
150
150
  is_default = True
151
151
 
152
152
  # The component map from a tag to a lambda that creates a component.
153
- component_map: dict[str, Any] = {}
153
+ component_map: dict[str, Any] = field(
154
+ default_factory=dict, is_javascript_property=False
155
+ )
154
156
 
155
157
  # The hash of the component map, generated at create() time.
156
- component_map_hash: str = ""
158
+ component_map_hash: str = field(default="", is_javascript_property=False)
157
159
 
158
160
  @classmethod
159
161
  def create(cls, *children, **props) -> Component:
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, TypedDict, TypeVar
5
+ from typing import TYPE_CHECKING, Any, TypedDict, TypeVar
6
6
 
7
7
  from reflex.components.component import Component, NoSSRComponent
8
8
  from reflex.components.core.cond import color_mode_cond
@@ -17,8 +17,9 @@ try:
17
17
 
18
18
  except ImportError:
19
19
  console.warn("Plotly is not installed. Please run `pip install plotly`.")
20
- Figure = Any
21
- Template = Any
20
+ if not TYPE_CHECKING:
21
+ Figure = Any
22
+ Template = Any
22
23
 
23
24
 
24
25
  def _event_points_data_signature(e0: Var) -> tuple[Var[list[Point]]]:
@@ -78,13 +79,13 @@ class Plotly(NoSSRComponent):
78
79
  is_default = True
79
80
 
80
81
  # The figure to display. This can be a plotly figure or a plotly data json.
81
- data: Var[Figure] # pyright: ignore [reportInvalidTypeForm]
82
+ data: Var[Figure]
82
83
 
83
84
  # The layout of the graph.
84
85
  layout: Var[dict]
85
86
 
86
87
  # The template for visual appearance of the graph.
87
- template: Var[Template] # pyright: ignore [reportInvalidTypeForm]
88
+ template: Var[Template]
88
89
 
89
90
  # The config of the graph.
90
91
  config: Var[dict]
@@ -213,6 +214,7 @@ const extractPoints = (points) => {
213
214
  Returns:
214
215
  The Plotly component.
215
216
  """
217
+ from plotly.graph_objs.layout import Template
216
218
  from plotly.io import templates
217
219
 
218
220
  responsive_template = color_mode_cond(
@@ -277,8 +279,12 @@ def dynamic_plotly_import(name: str, package: str) -> str:
277
279
  Returns:
278
280
  The dynamic import for the plotly component.
279
281
  """
282
+ library_import = f"import('{package}')"
283
+ mod_import = ".then((mod) => ({ default: createPlotlyComponent(mod) }))"
280
284
  return f"""
281
- const {name} = dynamic(() => import('{package}').then(mod => createPlotlyComponent(mod)), {{ssr: false}})
285
+ const {name} = ClientSide(lazy(() =>
286
+ {library_import}{mod_import}
287
+ ))
282
288
  """
283
289
 
284
290
 
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  from typing import Any, Literal, get_args
21
21
 
22
- from reflex.components.component import BaseComponent
22
+ from reflex.components.component import BaseComponent, field
23
23
  from reflex.components.core.cond import Cond, color_mode_cond, cond
24
24
  from reflex.components.lucide.icon import Icon
25
25
  from reflex.components.radix.themes.components.dropdown_menu import dropdown_menu
@@ -99,10 +99,12 @@ class ColorModeIconButton(IconButton):
99
99
  """Icon Button for toggling light / dark mode via toggle_color_mode."""
100
100
 
101
101
  # The position of the icon button. Follow document flow if None.
102
- position: LiteralPosition | Var[LiteralPosition] | None = None
102
+ position: LiteralPosition | Var[LiteralPosition] | None = field(
103
+ default=None, is_javascript_property=False
104
+ )
103
105
 
104
106
  # Allow picking the "system" value for the color mode.
105
- allow_system: bool = False
107
+ allow_system: bool = field(default=False, is_javascript_property=False)
106
108
 
107
109
  @classmethod
108
110
  def create(
@@ -168,9 +170,6 @@ class ColorModeIconButton(IconButton):
168
170
  **props,
169
171
  )
170
172
 
171
- def _exclude_props(self) -> list[str]:
172
- return ["position", "allow_system"]
173
-
174
173
 
175
174
  class ColorModeSwitch(Switch):
176
175
  """Switch for toggling light / dark mode via toggle_color_mode."""
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Sequence
6
- from typing import Any, ClassVar
6
+ from typing import Any, ClassVar, TypedDict
7
7
 
8
8
  from reflex.constants import EventTriggers
9
9
  from reflex.constants.colors import Color
@@ -663,6 +663,13 @@ class Reference(Recharts):
663
663
  is_front: Var[bool]
664
664
 
665
665
 
666
+ class Segment(TypedDict):
667
+ """A segment in a ReferenceLine or ReferenceArea."""
668
+
669
+ x: str | int
670
+ y: str | int
671
+
672
+
666
673
  class ReferenceLine(Reference):
667
674
  """A ReferenceLine component in Recharts."""
668
675
 
@@ -686,7 +693,7 @@ class ReferenceLine(Reference):
686
693
  _valid_children: ClassVar[list[str]] = ["Label"]
687
694
 
688
695
  # Array of endpoints in { x, y } format. These endpoints would be used to draw the ReferenceLine.
689
- segment: Sequence[Any] = []
696
+ segment: Var[Sequence[Segment]]
690
697
 
691
698
 
692
699
  class ReferenceDot(Reference):
@@ -204,3 +204,10 @@ class SpecialAttributes(enum.Enum):
204
204
  True if the attribute is special.
205
205
  """
206
206
  return attr.startswith(SPECIAL_ATTRS)
207
+
208
+
209
+ class ResetStylesheet(SimpleNamespace):
210
+ """Constants for CSS reset stylesheet."""
211
+
212
+ # The filename of the CSS reset file.
213
+ FILENAME = "__reflex_style_reset.css"
reflex/constants/route.py CHANGED
@@ -41,23 +41,30 @@ class RouteRegex(SimpleNamespace):
41
41
  _CLOSING_BRACKET = r"\]"
42
42
  _ARG_NAME = r"[a-zA-Z_]\w*"
43
43
 
44
+ # The regex for a valid arg name, e.g. "slug" in "[slug]"
45
+ _ARG_NAME_PATTERN = re.compile(_ARG_NAME)
46
+
47
+ SLUG = re.compile(r"[a-zA-Z0-9_-]+")
44
48
  # match a single arg (i.e. "[slug]"), returns the name of the arg
45
49
  ARG = re.compile(rf"{_OPENING_BRACKET}({_ARG_NAME}){_CLOSING_BRACKET}")
46
- # match a single catch-all arg (i.e. "[...slug]" or "[[...slug]]"), returns the name of the arg
47
- CATCHALL = re.compile(
48
- rf"({_OPENING_BRACKET}?{_OPENING_BRACKET}{_DOT_DOT_DOT}[^[{_CLOSING_BRACKET}]*{_CLOSING_BRACKET}?{_CLOSING_BRACKET})"
50
+ # match a single optional arg (i.e. "[[slug]]"), returns the name of the arg
51
+ OPTIONAL_ARG = re.compile(
52
+ rf"{_OPENING_BRACKET * 2}({_ARG_NAME}){_CLOSING_BRACKET * 2}"
49
53
  )
54
+
50
55
  # match a single non-optional catch-all arg (i.e. "[...slug]"), returns the name of the arg
51
56
  STRICT_CATCHALL = re.compile(
52
57
  rf"{_OPENING_BRACKET}{_DOT_DOT_DOT}({_ARG_NAME}){_CLOSING_BRACKET}"
53
58
  )
54
- # match a snigle optional catch-all arg (i.e. "[[...slug]]"), returns the name of the arg
55
- OPT_CATCHALL = re.compile(
59
+
60
+ # match a single optional catch-all arg (i.e. "[[...slug]]"), returns the name of the arg
61
+ OPTIONAL_CATCHALL = re.compile(
56
62
  rf"{_OPENING_BRACKET * 2}{_DOT_DOT_DOT}({_ARG_NAME}){_CLOSING_BRACKET * 2}"
57
63
  )
64
+
65
+ SPLAT_CATCHALL = "[[...splat]]"
58
66
  SINGLE_SEGMENT = "__SINGLE_SEGMENT__"
59
67
  DOUBLE_SEGMENT = "__DOUBLE_SEGMENT__"
60
- SINGLE_CATCHALL_SEGMENT = "__SINGLE_CATCHALL_SEGMENT__"
61
68
  DOUBLE_CATCHALL_SEGMENT = "__DOUBLE_CATCHALL_SEGMENT__"
62
69
 
63
70