Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 26 additions & 60 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
)
from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar, FunctionVar
from reflex.vars.number import ternary_operation
from reflex.vars.object import LiteralObjectVar, ObjectVar
from reflex.vars.object import ObjectVar
from reflex.vars.sequence import LiteralArrayVar, LiteralStringVar, StringVar


Expand Down Expand Up @@ -500,36 +500,16 @@ def _post_init(self, *args, **kwargs):
else:
continue

def determine_key(value: Any):
# Try to create a var from the value
key = value if isinstance(value, Var) else LiteralVar.create(value)

# Check that the var type is not None.
if key is None:
raise TypeError

return key

# Check whether the key is a component prop.
if is_var:
try:
kwargs[key] = determine_key(value)
kwargs[key] = LiteralVar.create(value)

# Get the passed type and the var type.
passed_type = kwargs[key]._var_type
expected_type = types.get_args(
types.get_field_type(type(self), key)
)[0]

# validate literal fields.
types.validate_literal(
key, value, expected_type, type(self).__name__
)
# Get the passed type and the var type.
passed_type = kwargs[key]._var_type
expected_type = (
type(expected_type.__args__[0])
if types.is_literal(expected_type)
else expected_type
)
except TypeError:
# If it is not a valid var, check the base types.
passed_type = type(value)
Expand Down Expand Up @@ -561,15 +541,19 @@ def determine_key(value: Any):
kwargs.pop(key, None)

# Place data_ and aria_ attributes into custom_attrs
special_attributes = tuple(
special_attributes = [
key
for key in kwargs
if key not in fields and SpecialAttributes.is_special(key)
)
]
if special_attributes:
custom_attrs = kwargs.setdefault("custom_attrs", {})
for key in special_attributes:
custom_attrs[format.to_kebab_case(key)] = kwargs.pop(key)
custom_attrs.update(
{
format.to_kebab_case(key): kwargs.pop(key)
for key in special_attributes
}
)

# Add style props to the component.
style = kwargs.get("style", {})
Expand Down Expand Up @@ -805,6 +789,18 @@ def _get_components_in_props(self) -> Sequence[BaseComponent]:
for component in _components_from(value)
]

@classmethod
def _validate_children(cls, children: tuple | list):
from reflex.utils.exceptions import ChildrenTypeError

for child in children:
if isinstance(child, (tuple, list)):
cls._validate_children(child)

# Make sure the child is a valid type.
if isinstance(child, dict) or not isinstance(child, ComponentChildTypes):
raise ChildrenTypeError(component=cls.__name__, child=child)

@classmethod
def create(cls: type[T], *children, **props) -> T:
"""Create the component.
Expand All @@ -819,24 +815,12 @@ def create(cls: type[T], *children, **props) -> T:
# Import here to avoid circular imports.
from reflex.components.base.bare import Bare
from reflex.components.base.fragment import Fragment
from reflex.utils.exceptions import ChildrenTypeError

# Filter out None props
props = {key: value for key, value in props.items() if value is not None}

def validate_children(children: tuple | list):
for child in children:
if isinstance(child, (tuple, list)):
validate_children(child)

# Make sure the child is a valid type.
if isinstance(child, dict) or not isinstance(
child, ComponentChildTypes
):
raise ChildrenTypeError(component=cls.__name__, child=child)

# Validate all the children.
validate_children(children)
cls._validate_children(children)

children_normalized = [
(
Expand Down Expand Up @@ -2577,25 +2561,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
else LiteralNoneVar.create(),
)

props = {}

special_props = []

for prop_str in tag["props"]:
if ":" not in prop_str:
special_props.append(Var(prop_str).to(ObjectVar))
continue
prop = prop_str.index(":")
key = prop_str[:prop]
value = prop_str[prop + 1 :]
props[key] = value

props = LiteralObjectVar.create(
{LiteralStringVar.create(k): Var(v) for k, v in props.items()}
)

for prop in special_props:
props = props.merge(prop)
props = Var("({" + ",".join(tag["props"]) + "})")

contents = tag["contents"] if tag["contents"] else None

Expand Down
18 changes: 12 additions & 6 deletions reflex/constants/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,19 @@ class MemoizationMode:
recursive: bool = True


DATA_UNDERSCORE = "data_"
DATA_DASH = "data-"
ARIA_UNDERSCORE = "aria_"
ARIA_DASH = "aria-"


class SpecialAttributes(enum.Enum):
"""Special attributes for components.

These are placed in custom_attrs and rendered as-is rather than converting
to a style prop.
"""

DATA_UNDERSCORE = "data_"
DATA_DASH = "data-"
ARIA_UNDERSCORE = "aria_"
ARIA_DASH = "aria-"

@classmethod
def is_special(cls, attr: str) -> bool:
"""Check if the attribute is special.
Expand All @@ -193,4 +194,9 @@ def is_special(cls, attr: str) -> bool:
Returns:
True if the attribute is special.
"""
return any(attr.startswith(value.value) for value in cls)
return (
attr.startswith(DATA_UNDERSCORE)
or attr.startswith(DATA_DASH)
or attr.startswith(ARIA_UNDERSCORE)
or attr.startswith(ARIA_DASH)
)
15 changes: 4 additions & 11 deletions reflex/utils/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,19 +436,12 @@ def format_props(*single_props, **key_value_props) -> list[str]:
The formatted props list.
"""
# Format all the props.
from reflex.vars.base import LiteralVar, Var
from reflex.vars import LiteralStringVar, LiteralVar, Var

return [
":".join(
[
str(name if "-" not in name else LiteralVar.create(name)),
str(
format_prop(
prop if isinstance(prop, Var) else LiteralVar.create(prop)
)
),
]
)
(str(LiteralStringVar.create(name)) if "-" in name else name)
+ ":"
+ str(format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop)))
for name, prop in sorted(key_value_props.items())
if prop is not None
] + [(f"...{LiteralVar.create(prop)!s}") for prop in single_props]
Expand Down
28 changes: 14 additions & 14 deletions tests/units/components/test_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -1221,10 +1221,10 @@ def test_stateful_banner():
assert isinstance(stateful_component, StatefulComponent)


TEST_VAR = LiteralVar.create("test")._replace(
TEST_VAR = LiteralVar.create("p")._replace(
merge_var_data=VarData(
hooks={"useTest": None},
imports={"test": [ImportVar(tag="test")]},
imports={"test": [ImportVar(tag="p")]},
state="Test",
)
)
Expand All @@ -1233,31 +1233,31 @@ def test_stateful_banner():
EVENT_CHAIN_VAR = TEST_VAR.to(EventChain)
ARG_VAR = Var(_js_expr="arg")

TEST_VAR_DICT_OF_DICT = LiteralVar.create({"a": {"b": "test"}})._replace(
TEST_VAR_DICT_OF_DICT = LiteralVar.create({"a": {"b": "p"}})._replace(
merge_var_data=TEST_VAR._var_data
)
FORMATTED_TEST_VAR_DICT_OF_DICT = LiteralVar.create({"a": {"b": "foopbar"}})._replace(
merge_var_data=TEST_VAR._var_data
)
FORMATTED_TEST_VAR_DICT_OF_DICT = LiteralVar.create(
{"a": {"b": "footestbar"}}
)._replace(merge_var_data=TEST_VAR._var_data)

TEST_VAR_LIST_OF_LIST = LiteralVar.create([["test"]])._replace(
TEST_VAR_LIST_OF_LIST = LiteralVar.create([["p"]])._replace(
merge_var_data=TEST_VAR._var_data
)
FORMATTED_TEST_VAR_LIST_OF_LIST = LiteralVar.create([["footestbar"]])._replace(
FORMATTED_TEST_VAR_LIST_OF_LIST = LiteralVar.create([["foopbar"]])._replace(
merge_var_data=TEST_VAR._var_data
)

TEST_VAR_LIST_OF_LIST_OF_LIST = LiteralVar.create([[["test"]]])._replace(
TEST_VAR_LIST_OF_LIST_OF_LIST = LiteralVar.create([[["p"]]])._replace(
merge_var_data=TEST_VAR._var_data
)
FORMATTED_TEST_VAR_LIST_OF_LIST_OF_LIST = LiteralVar.create([[["foopbar"]]])._replace(
merge_var_data=TEST_VAR._var_data
)
FORMATTED_TEST_VAR_LIST_OF_LIST_OF_LIST = LiteralVar.create(
[[["footestbar"]]]
)._replace(merge_var_data=TEST_VAR._var_data)

TEST_VAR_LIST_OF_DICT = LiteralVar.create([{"a": "test"}])._replace(
TEST_VAR_LIST_OF_DICT = LiteralVar.create([{"a": "p"}])._replace(
merge_var_data=TEST_VAR._var_data
)
FORMATTED_TEST_VAR_LIST_OF_DICT = LiteralVar.create([{"a": "footestbar"}])._replace(
FORMATTED_TEST_VAR_LIST_OF_DICT = LiteralVar.create([{"a": "foopbar"}])._replace(
merge_var_data=TEST_VAR._var_data
)

Expand Down