reflex 0.7.0a4__py3-none-any.whl → 0.7.1a1__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 (127) hide show
  1. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
  2. reflex/__init__.py +1 -0
  3. reflex/__init__.pyi +1 -0
  4. reflex/app.py +251 -68
  5. reflex/base.py +4 -10
  6. reflex/compiler/compiler.py +46 -12
  7. reflex/compiler/templates.py +1 -2
  8. reflex/compiler/utils.py +23 -14
  9. reflex/components/base/bare.py +109 -16
  10. reflex/components/component.py +179 -124
  11. reflex/components/core/__init__.py +1 -0
  12. reflex/components/core/__init__.pyi +1 -0
  13. reflex/components/core/auto_scroll.py +111 -0
  14. reflex/components/core/auto_scroll.pyi +284 -0
  15. reflex/components/core/banner.py +35 -5
  16. reflex/components/core/banner.pyi +398 -36
  17. reflex/components/core/breakpoints.py +1 -1
  18. reflex/components/core/cond.py +0 -8
  19. reflex/components/core/foreach.py +12 -2
  20. reflex/components/core/html.pyi +200 -19
  21. reflex/components/core/match.py +4 -4
  22. reflex/components/core/sticky.py +4 -30
  23. reflex/components/core/sticky.pyi +874 -90
  24. reflex/components/core/upload.py +3 -5
  25. reflex/components/core/upload.pyi +2 -4
  26. reflex/components/datadisplay/code.py +36 -10
  27. reflex/components/datadisplay/code.pyi +1 -1
  28. reflex/components/datadisplay/dataeditor.py +1 -3
  29. reflex/components/datadisplay/dataeditor.pyi +1 -3
  30. reflex/components/el/elements/base.py +95 -17
  31. reflex/components/el/elements/base.pyi +278 -19
  32. reflex/components/el/elements/forms.py +124 -102
  33. reflex/components/el/elements/forms.pyi +2787 -365
  34. reflex/components/el/elements/inline.py +24 -15
  35. reflex/components/el/elements/inline.pyi +5655 -546
  36. reflex/components/el/elements/media.py +79 -95
  37. reflex/components/el/elements/media.pyi +5167 -565
  38. reflex/components/el/elements/metadata.py +19 -17
  39. reflex/components/el/elements/metadata.pyi +841 -89
  40. reflex/components/el/elements/other.py +3 -5
  41. reflex/components/el/elements/other.pyi +1404 -137
  42. reflex/components/el/elements/scripts.py +10 -13
  43. reflex/components/el/elements/scripts.pyi +634 -65
  44. reflex/components/el/elements/sectioning.pyi +3001 -286
  45. reflex/components/el/elements/tables.py +14 -35
  46. reflex/components/el/elements/tables.pyi +2029 -218
  47. reflex/components/el/elements/typography.py +10 -13
  48. reflex/components/el/elements/typography.pyi +3014 -297
  49. reflex/components/lucide/icon.py +22 -6
  50. reflex/components/markdown/markdown.py +30 -10
  51. reflex/components/markdown/markdown.pyi +3 -2
  52. reflex/components/plotly/plotly.py +1 -3
  53. reflex/components/plotly/plotly.pyi +1 -3
  54. reflex/components/radix/primitives/form.pyi +624 -93
  55. reflex/components/radix/themes/color_mode.py +1 -1
  56. reflex/components/radix/themes/color_mode.pyi +213 -31
  57. reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
  58. reflex/components/radix/themes/components/badge.pyi +199 -18
  59. reflex/components/radix/themes/components/button.pyi +213 -31
  60. reflex/components/radix/themes/components/callout.pyi +1000 -95
  61. reflex/components/radix/themes/components/card.pyi +199 -18
  62. reflex/components/radix/themes/components/context_menu.py +79 -1
  63. reflex/components/radix/themes/components/context_menu.pyi +320 -1
  64. reflex/components/radix/themes/components/dialog.pyi +199 -18
  65. reflex/components/radix/themes/components/hover_card.pyi +199 -18
  66. reflex/components/radix/themes/components/icon_button.pyi +213 -31
  67. reflex/components/radix/themes/components/inset.pyi +199 -18
  68. reflex/components/radix/themes/components/popover.pyi +199 -18
  69. reflex/components/radix/themes/components/table.pyi +1437 -154
  70. reflex/components/radix/themes/components/text_area.py +2 -2
  71. reflex/components/radix/themes/components/text_area.pyi +201 -20
  72. reflex/components/radix/themes/components/text_field.py +1 -1
  73. reflex/components/radix/themes/components/text_field.pyi +444 -88
  74. reflex/components/radix/themes/layout/box.pyi +200 -19
  75. reflex/components/radix/themes/layout/center.pyi +199 -18
  76. reflex/components/radix/themes/layout/container.pyi +199 -18
  77. reflex/components/radix/themes/layout/flex.pyi +199 -18
  78. reflex/components/radix/themes/layout/grid.pyi +199 -18
  79. reflex/components/radix/themes/layout/list.pyi +604 -57
  80. reflex/components/radix/themes/layout/section.pyi +199 -18
  81. reflex/components/radix/themes/layout/spacer.pyi +199 -18
  82. reflex/components/radix/themes/layout/stack.pyi +597 -54
  83. reflex/components/radix/themes/typography/blockquote.pyi +200 -19
  84. reflex/components/radix/themes/typography/code.pyi +199 -18
  85. reflex/components/radix/themes/typography/heading.pyi +199 -18
  86. reflex/components/radix/themes/typography/link.pyi +238 -28
  87. reflex/components/radix/themes/typography/text.pyi +1394 -127
  88. reflex/components/react_player/react_player.py +1 -1
  89. reflex/components/react_player/react_player.pyi +1 -3
  90. reflex/components/sonner/toast.py +19 -1
  91. reflex/components/sonner/toast.pyi +10 -1
  92. reflex/components/tags/iter_tag.py +4 -0
  93. reflex/components/tags/tag.py +3 -3
  94. reflex/config.py +187 -28
  95. reflex/constants/__init__.py +2 -0
  96. reflex/constants/base.py +6 -0
  97. reflex/constants/compiler.py +9 -0
  98. reflex/constants/event.py +1 -0
  99. reflex/constants/installer.py +4 -5
  100. reflex/constants/utils.py +1 -3
  101. reflex/event.py +7 -16
  102. reflex/experimental/layout.pyi +597 -54
  103. reflex/py.typed +0 -0
  104. reflex/reflex.py +44 -48
  105. reflex/state.py +49 -44
  106. reflex/style.py +6 -4
  107. reflex/testing.py +2 -0
  108. reflex/utils/build.py +12 -0
  109. reflex/utils/console.py +4 -0
  110. reflex/utils/decorator.py +25 -0
  111. reflex/utils/exec.py +92 -34
  112. reflex/utils/format.py +35 -6
  113. reflex/utils/path_ops.py +32 -1
  114. reflex/utils/prerequisites.py +54 -10
  115. reflex/utils/processes.py +12 -13
  116. reflex/utils/serializers.py +20 -43
  117. reflex/utils/telemetry.py +4 -15
  118. reflex/utils/types.py +36 -66
  119. reflex/vars/base.py +53 -76
  120. reflex/vars/function.py +17 -5
  121. reflex/vars/number.py +1 -1
  122. reflex/vars/sequence.py +80 -4
  123. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/METADATA +4 -5
  124. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/RECORD +127 -123
  125. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/LICENSE +0 -0
  126. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/WHEEL +0 -0
  127. {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/entry_points.txt +0 -0
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import copy
6
6
  import dataclasses
7
+ import inspect
7
8
  import typing
8
9
  from abc import ABC, abstractmethod
9
10
  from functools import lru_cache, wraps
@@ -21,8 +22,12 @@ from typing import (
21
22
  Set,
22
23
  Type,
23
24
  Union,
25
+ get_args,
26
+ get_origin,
24
27
  )
25
28
 
29
+ from typing_extensions import Self
30
+
26
31
  import reflex.state
27
32
  from reflex.base import Base
28
33
  from reflex.compiler.templates import STATEFUL_COMPONENT
@@ -41,6 +46,7 @@ from reflex.constants import (
41
46
  from reflex.constants.compiler import SpecialAttributes
42
47
  from reflex.constants.state import FRONTEND_EVENT_STATE
43
48
  from reflex.event import (
49
+ EventActionsMixin,
44
50
  EventCallback,
45
51
  EventChain,
46
52
  EventHandler,
@@ -49,13 +55,7 @@ from reflex.event import (
49
55
  )
50
56
  from reflex.style import Style, format_as_emotion
51
57
  from reflex.utils import format, imports, types
52
- from reflex.utils.imports import (
53
- ImmutableParsedImportDict,
54
- ImportDict,
55
- ImportVar,
56
- ParsedImportDict,
57
- parse_imports,
58
- )
58
+ from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports
59
59
  from reflex.vars import VarData
60
60
  from reflex.vars.base import (
61
61
  CachedVarOperation,
@@ -179,6 +179,7 @@ ComponentStyle = Dict[
179
179
  Union[str, Type[BaseComponent], Callable, ComponentNamespace], Any
180
180
  ]
181
181
  ComponentChild = Union[types.PrimitiveType, Var, BaseComponent]
182
+ ComponentChildTypes = (*types.PrimitiveTypes, Var, BaseComponent)
182
183
 
183
184
 
184
185
  def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
@@ -191,11 +192,26 @@ def satisfies_type_hint(obj: Any, type_hint: Any) -> bool:
191
192
  Returns:
192
193
  Whether the object satisfies the type hint.
193
194
  """
194
- if isinstance(obj, LiteralVar):
195
- return types._isinstance(obj._var_value, type_hint)
196
- if isinstance(obj, Var):
197
- return types._issubclass(obj._var_type, type_hint)
198
- return types._isinstance(obj, type_hint)
195
+ return types._isinstance(obj, type_hint, nested=1)
196
+
197
+
198
+ def _components_from(
199
+ component_or_var: Union[BaseComponent, Var],
200
+ ) -> tuple[BaseComponent, ...]:
201
+ """Get the components from a component or Var.
202
+
203
+ Args:
204
+ component_or_var: The component or Var to get the components from.
205
+
206
+ Returns:
207
+ The components.
208
+ """
209
+ if isinstance(component_or_var, Var):
210
+ var_data = component_or_var._get_all_var_data()
211
+ return var_data.components if var_data else ()
212
+ if isinstance(component_or_var, BaseComponent):
213
+ return (component_or_var,)
214
+ return ()
199
215
 
200
216
 
201
217
  class Component(BaseComponent, ABC):
@@ -496,7 +512,7 @@ class Component(BaseComponent, ABC):
496
512
 
497
513
  # Remove any keys that were added as events.
498
514
  for key in kwargs["event_triggers"]:
499
- del kwargs[key]
515
+ kwargs.pop(key, None)
500
516
 
501
517
  # Place data_ and aria_ attributes into custom_attrs
502
518
  special_attributes = tuple(
@@ -672,13 +688,22 @@ class Component(BaseComponent, ABC):
672
688
  """
673
689
  return set()
674
690
 
691
+ @classmethod
692
+ def _are_fields_known(cls) -> bool:
693
+ """Check if all fields are known at compile time. True for most components.
694
+
695
+ Returns:
696
+ Whether all fields are known at compile time.
697
+ """
698
+ return True
699
+
675
700
  @classmethod
676
701
  @lru_cache(maxsize=None)
677
- def get_component_props(cls) -> set[str]:
678
- """Get the props that expected a component as value.
702
+ def _get_component_prop_names(cls) -> Set[str]:
703
+ """Get the names of the component props. NOTE: This assumes all fields are known.
679
704
 
680
705
  Returns:
681
- The components props.
706
+ The names of the component props.
682
707
  """
683
708
  return {
684
709
  name
@@ -687,8 +712,28 @@ class Component(BaseComponent, ABC):
687
712
  and types._issubclass(field.outer_type_, Component)
688
713
  }
689
714
 
715
+ def _get_components_in_props(self) -> Sequence[BaseComponent]:
716
+ """Get the components in the props.
717
+
718
+ Returns:
719
+ The components in the props
720
+ """
721
+ if self._are_fields_known():
722
+ return [
723
+ component
724
+ for name in self._get_component_prop_names()
725
+ for component in _components_from(getattr(self, name))
726
+ ]
727
+ return [
728
+ component
729
+ for prop in self.get_props()
730
+ if (value := getattr(self, prop)) is not None
731
+ and isinstance(value, (BaseComponent, Var))
732
+ for component in _components_from(value)
733
+ ]
734
+
690
735
  @classmethod
691
- def create(cls, *children, **props) -> Component:
736
+ def create(cls, *children, **props) -> Self:
692
737
  """Create the component.
693
738
 
694
739
  Args:
@@ -712,8 +757,8 @@ class Component(BaseComponent, ABC):
712
757
  validate_children(child)
713
758
 
714
759
  # Make sure the child is a valid type.
715
- if isinstance(child, dict) or not types._isinstance(
716
- child, ComponentChild
760
+ if isinstance(child, dict) or not isinstance(
761
+ child, ComponentChildTypes
717
762
  ):
718
763
  raise ChildrenTypeError(component=cls.__name__, child=child)
719
764
 
@@ -1143,6 +1188,9 @@ class Component(BaseComponent, ABC):
1143
1188
  if custom_code is not None:
1144
1189
  code.add(custom_code)
1145
1190
 
1191
+ for component in self._get_components_in_props():
1192
+ code |= component._get_all_custom_code()
1193
+
1146
1194
  # Add the custom code from add_custom_code method.
1147
1195
  for clz in self._iter_parent_classes_with_method("add_custom_code"):
1148
1196
  for item in clz.add_custom_code(self):
@@ -1170,7 +1218,7 @@ class Component(BaseComponent, ABC):
1170
1218
  The dynamic imports.
1171
1219
  """
1172
1220
  # Store the import in a set to avoid duplicates.
1173
- dynamic_imports = set()
1221
+ dynamic_imports: set[str] = set()
1174
1222
 
1175
1223
  # Get dynamic import for this component.
1176
1224
  dynamic_import = self._get_dynamic_imports()
@@ -1181,25 +1229,12 @@ class Component(BaseComponent, ABC):
1181
1229
  for child in self.children:
1182
1230
  dynamic_imports |= child._get_all_dynamic_imports()
1183
1231
 
1184
- for prop in self.get_component_props():
1185
- if getattr(self, prop) is not None:
1186
- dynamic_imports |= getattr(self, prop)._get_all_dynamic_imports()
1232
+ for component in self._get_components_in_props():
1233
+ dynamic_imports |= component._get_all_dynamic_imports()
1187
1234
 
1188
1235
  # Return the dynamic imports
1189
1236
  return dynamic_imports
1190
1237
 
1191
- def _get_props_imports(self) -> List[ParsedImportDict]:
1192
- """Get the imports needed for components props.
1193
-
1194
- Returns:
1195
- The imports for the components props of the component.
1196
- """
1197
- return [
1198
- getattr(self, prop)._get_all_imports()
1199
- for prop in self.get_component_props()
1200
- if getattr(self, prop) is not None
1201
- ]
1202
-
1203
1238
  def _should_transpile(self, dep: str | None) -> bool:
1204
1239
  """Check if a dependency should be transpiled.
1205
1240
 
@@ -1209,7 +1244,7 @@ class Component(BaseComponent, ABC):
1209
1244
  Returns:
1210
1245
  True if the dependency should be transpiled.
1211
1246
  """
1212
- return (
1247
+ return bool(self.transpile_packages) and (
1213
1248
  dep in self.transpile_packages
1214
1249
  or format.format_library_name(dep or "") in self.transpile_packages
1215
1250
  )
@@ -1292,9 +1327,10 @@ class Component(BaseComponent, ABC):
1292
1327
  event_imports = Imports.EVENTS if self.event_triggers else {}
1293
1328
 
1294
1329
  # Collect imports from Vars used directly by this component.
1295
- var_datas = [var._get_all_var_data() for var in self._get_vars()]
1296
- var_imports: List[ImmutableParsedImportDict] = [
1297
- var_data.imports for var_data in var_datas if var_data is not None
1330
+ var_imports = [
1331
+ var_data.imports
1332
+ for var in self._get_vars()
1333
+ if (var_data := var._get_all_var_data()) is not None
1298
1334
  ]
1299
1335
 
1300
1336
  added_import_dicts: list[ParsedImportDict] = []
@@ -1309,7 +1345,6 @@ class Component(BaseComponent, ABC):
1309
1345
  )
1310
1346
 
1311
1347
  return imports.merge_imports(
1312
- *self._get_props_imports(),
1313
1348
  self._get_dependencies_imports(),
1314
1349
  self._get_hooks_imports(),
1315
1350
  _imports,
@@ -1386,6 +1421,8 @@ class Component(BaseComponent, ABC):
1386
1421
  for k in var_data.hooks
1387
1422
  }
1388
1423
  )
1424
+ for component in var_data.components:
1425
+ vars_hooks.update(component._get_all_hooks())
1389
1426
  return vars_hooks
1390
1427
 
1391
1428
  def _get_events_hooks(self) -> dict[str, VarData | None]:
@@ -1534,6 +1571,9 @@ class Component(BaseComponent, ABC):
1534
1571
  refs.add(ref)
1535
1572
  for child in self.children:
1536
1573
  refs |= child._get_all_refs()
1574
+ for component in self._get_components_in_props():
1575
+ refs |= component._get_all_refs()
1576
+
1537
1577
  return refs
1538
1578
 
1539
1579
  def _get_all_custom_components(
@@ -1557,6 +1597,9 @@ class Component(BaseComponent, ABC):
1557
1597
  if not isinstance(child, Component):
1558
1598
  continue
1559
1599
  custom_components |= child._get_all_custom_components(seen=seen)
1600
+ for component in self._get_components_in_props():
1601
+ if isinstance(component, Component) and component.tag is not None:
1602
+ custom_components |= component._get_all_custom_components(seen=seen)
1560
1603
  return custom_components
1561
1604
 
1562
1605
  @property
@@ -1620,17 +1663,65 @@ class CustomComponent(Component):
1620
1663
  # The props of the component.
1621
1664
  props: Dict[str, Any] = {}
1622
1665
 
1623
- # Props that reference other components.
1624
- component_props: Dict[str, Component] = {}
1625
-
1626
- def __init__(self, *args, **kwargs):
1666
+ def __init__(self, **kwargs):
1627
1667
  """Initialize the custom component.
1628
1668
 
1629
1669
  Args:
1630
- *args: The args to pass to the component.
1631
1670
  **kwargs: The kwargs to pass to the component.
1632
1671
  """
1633
- super().__init__(*args, **kwargs)
1672
+ component_fn = kwargs.get("component_fn")
1673
+
1674
+ # Set the props.
1675
+ props_types = typing.get_type_hints(component_fn) if component_fn else {}
1676
+ props = {key: value for key, value in kwargs.items() if key in props_types}
1677
+ kwargs = {key: value for key, value in kwargs.items() if key not in props_types}
1678
+
1679
+ event_types = {
1680
+ key
1681
+ for key in props
1682
+ if (
1683
+ (get_origin((annotation := props_types.get(key))) or annotation)
1684
+ == EventHandler
1685
+ )
1686
+ }
1687
+
1688
+ def get_args_spec(key: str) -> types.ArgsSpec | Sequence[types.ArgsSpec]:
1689
+ type_ = props_types[key]
1690
+
1691
+ return (
1692
+ args[0]
1693
+ if (args := get_args(type_))
1694
+ else (
1695
+ annotation_args[1]
1696
+ if get_origin(
1697
+ (
1698
+ annotation := inspect.getfullargspec(
1699
+ component_fn
1700
+ ).annotations[key]
1701
+ )
1702
+ )
1703
+ is typing.Annotated
1704
+ and (annotation_args := get_args(annotation))
1705
+ else no_args_event_spec
1706
+ )
1707
+ )
1708
+
1709
+ super().__init__(
1710
+ event_triggers={
1711
+ key: EventChain.create(
1712
+ value=props[key],
1713
+ args_spec=get_args_spec(key),
1714
+ key=key,
1715
+ )
1716
+ for key in event_types
1717
+ },
1718
+ **kwargs,
1719
+ )
1720
+
1721
+ to_camel_cased_props = {
1722
+ format.to_camel_case(key) for key in props if key not in event_types
1723
+ }
1724
+ self.get_props = lambda: to_camel_cased_props # pyright: ignore [reportIncompatibleVariableOverride]
1634
1725
 
1635
1726
  # Unset the style.
1636
1727
  self.style = Style()
@@ -1638,51 +1729,36 @@ class CustomComponent(Component):
1638
1729
  # Set the tag to the name of the function.
1639
1730
  self.tag = format.to_title_case(self.component_fn.__name__)
1640
1731
 
1641
- # Get the event triggers defined in the component declaration.
1642
- event_triggers_in_component_declaration = self.get_event_triggers()
1643
-
1644
- # Set the props.
1645
- props = typing.get_type_hints(self.component_fn)
1646
- for key, value in kwargs.items():
1732
+ for key, value in props.items():
1647
1733
  # Skip kwargs that are not props.
1648
- if key not in props:
1734
+ if key not in props_types:
1649
1735
  continue
1650
1736
 
1737
+ camel_cased_key = format.to_camel_case(key)
1738
+
1651
1739
  # Get the type based on the annotation.
1652
- type_ = props[key]
1740
+ type_ = props_types[key]
1653
1741
 
1654
1742
  # Handle event chains.
1655
- if types._issubclass(type_, EventChain):
1656
- value = EventChain.create(
1657
- value=value,
1658
- args_spec=event_triggers_in_component_declaration.get(
1659
- key, no_args_event_spec
1660
- ),
1661
- key=key,
1743
+ if types._issubclass(type_, EventActionsMixin):
1744
+ inspect.getfullargspec(component_fn).annotations[key]
1745
+ self.props[camel_cased_key] = EventChain.create(
1746
+ value=value, args_spec=get_args_spec(key), key=key
1662
1747
  )
1663
- self.props[format.to_camel_case(key)] = value
1664
1748
  continue
1665
1749
 
1666
- # Handle subclasses of Base.
1667
- if isinstance(value, Base):
1668
- base_value = LiteralVar.create(value)
1669
-
1670
- # Track hooks and imports associated with Component instances.
1671
- if base_value is not None and isinstance(value, Component):
1672
- self.component_props[key] = value
1673
- value = base_value._replace(
1674
- merge_var_data=VarData(
1675
- imports=value._get_all_imports(),
1676
- hooks=value._get_all_hooks(),
1677
- )
1678
- )
1679
- else:
1680
- value = base_value
1681
- else:
1682
- value = LiteralVar.create(value)
1750
+ value = LiteralVar.create(value)
1751
+ self.props[camel_cased_key] = value
1752
+ setattr(self, camel_cased_key, value)
1753
+
1754
+ @classmethod
1755
+ def _are_fields_known(cls) -> bool:
1756
+ """Check if the fields are known.
1683
1757
 
1684
- # Set the prop.
1685
- self.props[format.to_camel_case(key)] = value
1758
+ Returns:
1759
+ Whether the fields are known.
1760
+ """
1761
+ return False
1686
1762
 
1687
1763
  def __eq__(self, other: Any) -> bool:
1688
1764
  """Check if the component is equal to another.
@@ -1704,7 +1780,7 @@ class CustomComponent(Component):
1704
1780
  return hash(self.tag)
1705
1781
 
1706
1782
  @classmethod
1707
- def get_props(cls) -> Set[str]: # pyright: ignore [reportIncompatibleVariableOverride]
1783
+ def get_props(cls) -> Set[str]:
1708
1784
  """Get the props for the component.
1709
1785
 
1710
1786
  Returns:
@@ -1741,27 +1817,8 @@ class CustomComponent(Component):
1741
1817
  seen=seen
1742
1818
  )
1743
1819
 
1744
- # Fetch custom components from props as well.
1745
- for child_component in self.component_props.values():
1746
- if child_component.tag is None:
1747
- continue
1748
- if child_component.tag not in seen:
1749
- seen.add(child_component.tag)
1750
- if isinstance(child_component, CustomComponent):
1751
- custom_components |= {child_component}
1752
- custom_components |= child_component._get_all_custom_components(
1753
- seen=seen
1754
- )
1755
1820
  return custom_components
1756
1821
 
1757
- def _render(self) -> Tag:
1758
- """Define how to render the component in React.
1759
-
1760
- Returns:
1761
- The tag to render.
1762
- """
1763
- return super()._render(props=self.props)
1764
-
1765
1822
  def get_prop_vars(self) -> List[Var]:
1766
1823
  """Get the prop vars.
1767
1824
 
@@ -1772,33 +1829,18 @@ class CustomComponent(Component):
1772
1829
  Var(
1773
1830
  _js_expr=name,
1774
1831
  _var_type=(
1775
- prop._var_type if types._isinstance(prop, Var) else type(prop)
1832
+ prop._var_type
1833
+ if isinstance(prop, Var)
1834
+ else (
1835
+ type(prop)
1836
+ if not isinstance(prop, EventActionsMixin)
1837
+ else EventChain
1838
+ )
1776
1839
  ),
1777
1840
  ).guess_type()
1778
1841
  for name, prop in self.props.items()
1779
1842
  ]
1780
1843
 
1781
- def _get_vars(
1782
- self, include_children: bool = False, ignore_ids: set[int] | None = None
1783
- ) -> Iterator[Var]:
1784
- """Walk all Vars used in this component.
1785
-
1786
- Args:
1787
- include_children: Whether to include Vars from children.
1788
- ignore_ids: The ids to ignore.
1789
-
1790
- Yields:
1791
- Each var referenced by the component (props, styles, event handlers).
1792
- """
1793
- ignore_ids = ignore_ids or set()
1794
- yield from super()._get_vars(
1795
- include_children=include_children, ignore_ids=ignore_ids
1796
- )
1797
- yield from filter(lambda prop: isinstance(prop, Var), self.props.values())
1798
- yield from self.get_component(self)._get_vars(
1799
- include_children=include_children, ignore_ids=ignore_ids
1800
- )
1801
-
1802
1844
  @lru_cache(maxsize=None) # noqa: B019
1803
1845
  def get_component(self) -> Component:
1804
1846
  """Render the component.
@@ -2486,6 +2528,7 @@ class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
2486
2528
  The VarData for the var.
2487
2529
  """
2488
2530
  return VarData.merge(
2531
+ self._var_data,
2489
2532
  VarData(
2490
2533
  imports={
2491
2534
  "@emotion/react": [
@@ -2528,9 +2571,21 @@ class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
2528
2571
  Returns:
2529
2572
  The var.
2530
2573
  """
2574
+ var_datas = [
2575
+ var_data
2576
+ for var in value._get_vars(include_children=True)
2577
+ if (var_data := var._get_all_var_data())
2578
+ ]
2579
+
2531
2580
  return LiteralComponentVar(
2532
2581
  _js_expr="",
2533
2582
  _var_type=type(value),
2534
- _var_data=_var_data,
2583
+ _var_data=VarData.merge(
2584
+ _var_data,
2585
+ *var_datas,
2586
+ VarData(
2587
+ components=(value,),
2588
+ ),
2589
+ ),
2535
2590
  _var_value=value,
2536
2591
  )
@@ -48,6 +48,7 @@ _SUBMOD_ATTRS: dict[str, list[str]] = {
48
48
  "get_upload_url",
49
49
  "selected_files",
50
50
  ],
51
+ "auto_scroll": ["auto_scroll"],
51
52
  }
52
53
 
53
54
  __getattr__, __dir__, __all__ = lazy_loader.attach(
@@ -4,6 +4,7 @@
4
4
  # ------------------------------------------------------
5
5
 
6
6
  from . import layout as layout
7
+ from .auto_scroll import auto_scroll as auto_scroll
7
8
  from .banner import ConnectionBanner as ConnectionBanner
8
9
  from .banner import ConnectionModal as ConnectionModal
9
10
  from .banner import ConnectionPulser as ConnectionPulser
@@ -0,0 +1,111 @@
1
+ """A component that automatically scrolls to the bottom when new content is added."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from reflex.components.el.elements.typography import Div
6
+ from reflex.constants.compiler import MemoizationDisposition, MemoizationMode
7
+ from reflex.utils.imports import ImportDict
8
+ from reflex.vars.base import Var, get_unique_variable_name
9
+
10
+
11
+ class AutoScroll(Div):
12
+ """A div that automatically scrolls to the bottom when new content is added."""
13
+
14
+ _memoization_mode = MemoizationMode(disposition=MemoizationDisposition.ALWAYS)
15
+
16
+ @classmethod
17
+ def create(cls, *children, **props):
18
+ """Create an AutoScroll component.
19
+
20
+ Args:
21
+ *children: The children of the component.
22
+ **props: The props of the component.
23
+
24
+ Returns:
25
+ An AutoScroll component.
26
+ """
27
+ props.setdefault("overflow", "auto")
28
+ props.setdefault("id", get_unique_variable_name())
29
+ return super().create(*children, **props)
30
+
31
+ def add_imports(self) -> ImportDict | list[ImportDict]:
32
+ """Add imports required for the component.
33
+
34
+ Returns:
35
+ The imports required for the component.
36
+ """
37
+ return {"react": ["useEffect", "useRef"]}
38
+
39
+ def add_hooks(self) -> list[str | Var]:
40
+ """Add hooks required for the component.
41
+
42
+ Returns:
43
+ The hooks required for the component.
44
+ """
45
+ ref_name = self.get_ref()
46
+ return [
47
+ "const containerRef = useRef(null);",
48
+ "const wasNearBottom = useRef(false);",
49
+ "const hadScrollbar = useRef(false);",
50
+ f"""
51
+ const checkIfNearBottom = () => {{
52
+ if (!{ref_name}.current) return;
53
+
54
+ const container = {ref_name}.current;
55
+ const nearBottomThreshold = 50; // pixels from bottom to trigger auto-scroll
56
+
57
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
58
+
59
+ wasNearBottom.current = distanceFromBottom <= nearBottomThreshold;
60
+
61
+ // Track if container had a scrollbar
62
+ hadScrollbar.current = container.scrollHeight > container.clientHeight;
63
+ }};
64
+ """,
65
+ f"""
66
+ const scrollToBottomIfNeeded = () => {{
67
+ if (!{ref_name}.current) return;
68
+
69
+ const container = {ref_name}.current;
70
+ const hasScrollbarNow = container.scrollHeight > container.clientHeight;
71
+
72
+ // Scroll if:
73
+ // 1. User was near bottom, OR
74
+ // 2. Container didn't have scrollbar before but does now
75
+ if (wasNearBottom.current || (!hadScrollbar.current && hasScrollbarNow)) {{
76
+ container.scrollTop = container.scrollHeight;
77
+ }}
78
+
79
+ // Update scrollbar state for next check
80
+ hadScrollbar.current = hasScrollbarNow;
81
+ }};
82
+ """,
83
+ f"""
84
+ useEffect(() => {{
85
+ const container = {ref_name}.current;
86
+ if (!container) return;
87
+
88
+ // Create ResizeObserver to detect height changes
89
+ const resizeObserver = new ResizeObserver(() => {{
90
+ scrollToBottomIfNeeded();
91
+ }});
92
+
93
+ // Track scroll position before height changes
94
+ container.addEventListener('scroll', checkIfNearBottom);
95
+
96
+ // Initial check
97
+ checkIfNearBottom();
98
+
99
+ // Observe container for size changes
100
+ resizeObserver.observe(container);
101
+
102
+ return () => {{
103
+ container.removeEventListener('scroll', checkIfNearBottom);
104
+ resizeObserver.disconnect();
105
+ }};
106
+ }});
107
+ """,
108
+ ]
109
+
110
+
111
+ auto_scroll = AutoScroll.create