
* StatefulComponent event handler useCallback dependency array Ensure the useCallback dependency array for event handlers also handled destructuring objects and the `...` rest parameter. Avoid hooks that are not variable declarations. * Abstract color_mode related Var creation * Allow `set_color_mode` to take a parameter at compile time * Update type hinting of `Var._replace` to indicate that it returns BaseVar * color_mode_button with allow_system=True uses new set_color_mode API `set_color_mode` is now a CallableVar and uses very similar logic internally, so this bit of code can be replaced. * Fix for pydantic v1.10.17
212 lines
6.2 KiB
Python
212 lines
6.2 KiB
Python
"""Create a list of components from an iterable."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, Optional, Union, overload
|
|
|
|
from reflex.components.base.fragment import Fragment
|
|
from reflex.components.component import BaseComponent, Component, MemoizationLeaf
|
|
from reflex.components.tags import CondTag, Tag
|
|
from reflex.constants import Dirs
|
|
from reflex.constants.colors import Color
|
|
from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
|
|
from reflex.utils import format
|
|
from reflex.utils.imports import ImportDict, ImportVar
|
|
from reflex.vars import BaseVar, Var, VarData
|
|
|
|
_IS_TRUE_IMPORT: ImportDict = {
|
|
f"/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
|
|
}
|
|
|
|
|
|
class Cond(MemoizationLeaf):
|
|
"""Render one of two components based on a condition."""
|
|
|
|
# The cond to determine which component to render.
|
|
cond: Var[Any]
|
|
|
|
# The component to render if the cond is true.
|
|
comp1: BaseComponent = None # type: ignore
|
|
|
|
# The component to render if the cond is false.
|
|
comp2: BaseComponent = None # type: ignore
|
|
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
cond: Var,
|
|
comp1: BaseComponent,
|
|
comp2: Optional[BaseComponent] = None,
|
|
) -> Component:
|
|
"""Create a conditional component.
|
|
|
|
Args:
|
|
cond: The cond to determine which component to render.
|
|
comp1: The component to render if the cond is true.
|
|
comp2: The component to render if the cond is false.
|
|
|
|
Returns:
|
|
The conditional component.
|
|
"""
|
|
# Wrap everything in fragments.
|
|
if comp1.__class__.__name__ != "Fragment":
|
|
comp1 = Fragment.create(comp1)
|
|
if comp2 is None or comp2.__class__.__name__ != "Fragment":
|
|
comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
|
|
return Fragment.create(
|
|
cls(
|
|
cond=cond,
|
|
comp1=comp1,
|
|
comp2=comp2,
|
|
children=[comp1, comp2],
|
|
)
|
|
)
|
|
|
|
def _get_props_imports(self):
|
|
"""Get the imports needed for component's props.
|
|
|
|
Returns:
|
|
The imports for the component's props of the component.
|
|
"""
|
|
return []
|
|
|
|
def _render(self) -> Tag:
|
|
return CondTag(
|
|
cond=self.cond,
|
|
true_value=self.comp1.render(),
|
|
false_value=self.comp2.render(),
|
|
)
|
|
|
|
def render(self) -> Dict:
|
|
"""Render the component.
|
|
|
|
Returns:
|
|
The dictionary for template of component.
|
|
"""
|
|
tag = self._render()
|
|
return dict(
|
|
tag.add_props(
|
|
**self.event_triggers,
|
|
key=self.key,
|
|
sx=self.style,
|
|
id=self.id,
|
|
class_name=self.class_name,
|
|
).set(
|
|
props=tag.format_props(),
|
|
),
|
|
cond_state=f"isTrue({self.cond._var_full_name})",
|
|
)
|
|
|
|
def add_imports(self) -> ImportDict:
|
|
"""Add imports for the Cond component.
|
|
|
|
Returns:
|
|
The import dict for the component.
|
|
"""
|
|
cond_imports: dict[str, str | ImportVar | list[str | ImportVar]] = getattr(
|
|
self.cond._var_data, "imports", {}
|
|
)
|
|
return {**cond_imports, **_IS_TRUE_IMPORT}
|
|
|
|
|
|
@overload
|
|
def cond(condition: Any, c1: Component, c2: Any) -> Component: ...
|
|
|
|
|
|
@overload
|
|
def cond(condition: Any, c1: Component) -> Component: ...
|
|
|
|
|
|
@overload
|
|
def cond(condition: Any, c1: Any, c2: Any) -> BaseVar: ...
|
|
|
|
|
|
def cond(condition: Any, c1: Any, c2: Any = None):
|
|
"""Create a conditional component or Prop.
|
|
|
|
Args:
|
|
condition: The cond to determine which component to render.
|
|
c1: The component or prop to render if the cond_var is true.
|
|
c2: The component or prop to render if the cond_var is false.
|
|
|
|
Returns:
|
|
The conditional component.
|
|
|
|
Raises:
|
|
ValueError: If the arguments are invalid.
|
|
"""
|
|
var_datas: list[VarData | None] = [
|
|
VarData( # type: ignore
|
|
imports=_IS_TRUE_IMPORT,
|
|
),
|
|
]
|
|
|
|
# Convert the condition to a Var.
|
|
cond_var = Var.create(condition)
|
|
assert cond_var is not None, "The condition must be set."
|
|
|
|
# If the first component is a component, create a Cond component.
|
|
if isinstance(c1, BaseComponent):
|
|
assert c2 is None or isinstance(
|
|
c2, BaseComponent
|
|
), "Both arguments must be components."
|
|
return Cond.create(cond_var, c1, c2)
|
|
if isinstance(c1, Var):
|
|
var_datas.append(c1._var_data)
|
|
|
|
# Otherwise, create a conditional Var.
|
|
# Check that the second argument is valid.
|
|
if isinstance(c2, BaseComponent):
|
|
raise ValueError("Both arguments must be props.")
|
|
if c2 is None:
|
|
raise ValueError("For conditional vars, the second argument must be set.")
|
|
if isinstance(c2, Var):
|
|
var_datas.append(c2._var_data)
|
|
|
|
def create_var(cond_part):
|
|
return Var.create_safe(
|
|
cond_part,
|
|
_var_is_string=isinstance(cond_part, (str, Color)),
|
|
)
|
|
|
|
# convert the truth and false cond parts into vars so the _var_data can be obtained.
|
|
c1 = create_var(c1)
|
|
c2 = create_var(c2)
|
|
var_datas.extend([c1._var_data, c2._var_data])
|
|
|
|
c1_type = c1._var_type if isinstance(c1, Var) else type(c1)
|
|
c2_type = c2._var_type if isinstance(c2, Var) else type(c2)
|
|
|
|
var_type = c1_type if c1_type == c2_type else Union[c1_type, c2_type]
|
|
|
|
# Create the conditional var.
|
|
return cond_var._replace(
|
|
_var_name=format.format_cond(
|
|
cond=cond_var._var_full_name,
|
|
true_value=c1,
|
|
false_value=c2,
|
|
is_prop=True,
|
|
),
|
|
_var_type=var_type,
|
|
_var_is_local=False,
|
|
_var_full_name_needs_state_prefix=False,
|
|
merge_var_data=VarData.merge(*var_datas),
|
|
)
|
|
|
|
|
|
def color_mode_cond(light: Any, dark: Any = None) -> Var | Component:
|
|
"""Create a component or Prop based on color_mode.
|
|
|
|
Args:
|
|
light: The component or prop to render if color_mode is default
|
|
dark: The component or prop to render if color_mode is non-default
|
|
|
|
Returns:
|
|
The conditional component or prop.
|
|
"""
|
|
return cond(
|
|
resolved_color_mode == Var.create(LIGHT_COLOR_MODE, _var_is_string=True),
|
|
light,
|
|
dark,
|
|
)
|