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.
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -1
- reflex/__init__.py +1 -0
- reflex/__init__.pyi +1 -0
- reflex/app.py +251 -68
- reflex/base.py +4 -10
- reflex/compiler/compiler.py +46 -12
- reflex/compiler/templates.py +1 -2
- reflex/compiler/utils.py +23 -14
- reflex/components/base/bare.py +109 -16
- reflex/components/component.py +179 -124
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +1 -0
- reflex/components/core/auto_scroll.py +111 -0
- reflex/components/core/auto_scroll.pyi +284 -0
- reflex/components/core/banner.py +35 -5
- reflex/components/core/banner.pyi +398 -36
- reflex/components/core/breakpoints.py +1 -1
- reflex/components/core/cond.py +0 -8
- reflex/components/core/foreach.py +12 -2
- reflex/components/core/html.pyi +200 -19
- reflex/components/core/match.py +4 -4
- reflex/components/core/sticky.py +4 -30
- reflex/components/core/sticky.pyi +874 -90
- reflex/components/core/upload.py +3 -5
- reflex/components/core/upload.pyi +2 -4
- reflex/components/datadisplay/code.py +36 -10
- reflex/components/datadisplay/code.pyi +1 -1
- reflex/components/datadisplay/dataeditor.py +1 -3
- reflex/components/datadisplay/dataeditor.pyi +1 -3
- reflex/components/el/elements/base.py +95 -17
- reflex/components/el/elements/base.pyi +278 -19
- reflex/components/el/elements/forms.py +124 -102
- reflex/components/el/elements/forms.pyi +2787 -365
- reflex/components/el/elements/inline.py +24 -15
- reflex/components/el/elements/inline.pyi +5655 -546
- reflex/components/el/elements/media.py +79 -95
- reflex/components/el/elements/media.pyi +5167 -565
- reflex/components/el/elements/metadata.py +19 -17
- reflex/components/el/elements/metadata.pyi +841 -89
- reflex/components/el/elements/other.py +3 -5
- reflex/components/el/elements/other.pyi +1404 -137
- reflex/components/el/elements/scripts.py +10 -13
- reflex/components/el/elements/scripts.pyi +634 -65
- reflex/components/el/elements/sectioning.pyi +3001 -286
- reflex/components/el/elements/tables.py +14 -35
- reflex/components/el/elements/tables.pyi +2029 -218
- reflex/components/el/elements/typography.py +10 -13
- reflex/components/el/elements/typography.pyi +3014 -297
- reflex/components/lucide/icon.py +22 -6
- reflex/components/markdown/markdown.py +30 -10
- reflex/components/markdown/markdown.pyi +3 -2
- reflex/components/plotly/plotly.py +1 -3
- reflex/components/plotly/plotly.pyi +1 -3
- reflex/components/radix/primitives/form.pyi +624 -93
- reflex/components/radix/themes/color_mode.py +1 -1
- reflex/components/radix/themes/color_mode.pyi +213 -31
- reflex/components/radix/themes/components/alert_dialog.pyi +199 -18
- reflex/components/radix/themes/components/badge.pyi +199 -18
- reflex/components/radix/themes/components/button.pyi +213 -31
- reflex/components/radix/themes/components/callout.pyi +1000 -95
- reflex/components/radix/themes/components/card.pyi +199 -18
- reflex/components/radix/themes/components/context_menu.py +79 -1
- reflex/components/radix/themes/components/context_menu.pyi +320 -1
- reflex/components/radix/themes/components/dialog.pyi +199 -18
- reflex/components/radix/themes/components/hover_card.pyi +199 -18
- reflex/components/radix/themes/components/icon_button.pyi +213 -31
- reflex/components/radix/themes/components/inset.pyi +199 -18
- reflex/components/radix/themes/components/popover.pyi +199 -18
- reflex/components/radix/themes/components/table.pyi +1437 -154
- reflex/components/radix/themes/components/text_area.py +2 -2
- reflex/components/radix/themes/components/text_area.pyi +201 -20
- reflex/components/radix/themes/components/text_field.py +1 -1
- reflex/components/radix/themes/components/text_field.pyi +444 -88
- reflex/components/radix/themes/layout/box.pyi +200 -19
- reflex/components/radix/themes/layout/center.pyi +199 -18
- reflex/components/radix/themes/layout/container.pyi +199 -18
- reflex/components/radix/themes/layout/flex.pyi +199 -18
- reflex/components/radix/themes/layout/grid.pyi +199 -18
- reflex/components/radix/themes/layout/list.pyi +604 -57
- reflex/components/radix/themes/layout/section.pyi +199 -18
- reflex/components/radix/themes/layout/spacer.pyi +199 -18
- reflex/components/radix/themes/layout/stack.pyi +597 -54
- reflex/components/radix/themes/typography/blockquote.pyi +200 -19
- reflex/components/radix/themes/typography/code.pyi +199 -18
- reflex/components/radix/themes/typography/heading.pyi +199 -18
- reflex/components/radix/themes/typography/link.pyi +238 -28
- reflex/components/radix/themes/typography/text.pyi +1394 -127
- reflex/components/react_player/react_player.py +1 -1
- reflex/components/react_player/react_player.pyi +1 -3
- reflex/components/sonner/toast.py +19 -1
- reflex/components/sonner/toast.pyi +10 -1
- reflex/components/tags/iter_tag.py +4 -0
- reflex/components/tags/tag.py +3 -3
- reflex/config.py +187 -28
- reflex/constants/__init__.py +2 -0
- reflex/constants/base.py +6 -0
- reflex/constants/compiler.py +9 -0
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +4 -5
- reflex/constants/utils.py +1 -3
- reflex/event.py +7 -16
- reflex/experimental/layout.pyi +597 -54
- reflex/py.typed +0 -0
- reflex/reflex.py +44 -48
- reflex/state.py +49 -44
- reflex/style.py +6 -4
- reflex/testing.py +2 -0
- reflex/utils/build.py +12 -0
- reflex/utils/console.py +4 -0
- reflex/utils/decorator.py +25 -0
- reflex/utils/exec.py +92 -34
- reflex/utils/format.py +35 -6
- reflex/utils/path_ops.py +32 -1
- reflex/utils/prerequisites.py +54 -10
- reflex/utils/processes.py +12 -13
- reflex/utils/serializers.py +20 -43
- reflex/utils/telemetry.py +4 -15
- reflex/utils/types.py +36 -66
- reflex/vars/base.py +53 -76
- reflex/vars/function.py +17 -5
- reflex/vars/number.py +1 -1
- reflex/vars/sequence.py +80 -4
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/METADATA +4 -5
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/RECORD +127 -123
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/LICENSE +0 -0
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/WHEEL +0 -0
- {reflex-0.7.0a4.dist-info → reflex-0.7.1a1.dist-info}/entry_points.txt +0 -0
reflex/components/component.py
CHANGED
|
@@ -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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
|
678
|
-
"""Get the props
|
|
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
|
|
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) ->
|
|
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
|
|
716
|
-
child,
|
|
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
|
|
1185
|
-
|
|
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
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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_ =
|
|
1740
|
+
type_ = props_types[key]
|
|
1653
1741
|
|
|
1654
1742
|
# Handle event chains.
|
|
1655
|
-
if types._issubclass(type_,
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
args_spec=
|
|
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
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
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
|
-
|
|
1685
|
-
|
|
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]:
|
|
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
|
|
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=
|
|
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
|
)
|
|
@@ -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
|