Răsfoiți Sursa

Support aria and data props (#4149)

* Support aria and data props

* Fix busted docstring

* Ignore special_attributes logic for defined props

* simplify special attribute checking logic

avoid special cases in the special case handling code 🙄
Masen Furer 7 luni în urmă
părinte
comite
1d268f8b13

+ 12 - 2
reflex/components/component.py

@@ -36,6 +36,7 @@ from reflex.constants import (
     MemoizationMode,
     PageNames,
 )
+from reflex.constants.compiler import SpecialAttributes
 from reflex.event import (
     EventChain,
     EventChainVar,
@@ -474,6 +475,17 @@ class Component(BaseComponent, ABC):
         for key in kwargs["event_triggers"]:
             del kwargs[key]
 
+        # Place data_ and aria_ attributes into custom_attrs
+        special_attributes = tuple(
+            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)
+
         # Add style props to the component.
         style = kwargs.get("style", {})
         if isinstance(style, List):
@@ -493,8 +505,6 @@ class Component(BaseComponent, ABC):
                 **{attr: value for attr, value in kwargs.items() if attr not in fields},
             }
         )
-        if "custom_attrs" not in kwargs:
-            kwargs["custom_attrs"] = {}
 
         # Convert class_name to str if it's list
         class_name = kwargs.get("class_name", "")

+ 25 - 0
reflex/constants/compiler.py

@@ -160,3 +160,28 @@ class MemoizationMode(Base):
 
     # Whether children of this component should be memoized first.
     recursive: bool = True
+
+
+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.
+
+        Args:
+            attr: the attribute to check
+
+        Returns:
+            True if the attribute is special.
+        """
+        return any(attr.startswith(value.value) for value in cls)

+ 53 - 0
tests/units/components/test_component.py

@@ -2217,3 +2217,56 @@ class TriggerState(rx.State):
 )
 def test_has_state_event_triggers(component, output):
     assert component._has_stateful_event_triggers() == output
+
+
+class SpecialComponent(Box):
+    """A special component with custom attributes."""
+
+    data_prop: Var[str]
+    aria_prop: Var[str]
+
+
+@pytest.mark.parametrize(
+    ("component_kwargs", "exp_custom_attrs", "exp_style"),
+    [
+        (
+            {"data_test": "test", "aria_test": "test"},
+            {"data-test": "test", "aria-test": "test"},
+            {},
+        ),
+        (
+            {"data-test": "test", "aria-test": "test"},
+            {"data-test": "test", "aria-test": "test"},
+            {},
+        ),
+        (
+            {"custom_attrs": {"data-existing": "test"}, "data_new": "test"},
+            {"data-existing": "test", "data-new": "test"},
+            {},
+        ),
+        (
+            {"data_test": "test", "data_prop": "prop"},
+            {"data-test": "test"},
+            {},
+        ),
+        (
+            {"aria_test": "test", "aria_prop": "prop"},
+            {"aria-test": "test"},
+            {},
+        ),
+    ],
+)
+def test_special_props(component_kwargs, exp_custom_attrs, exp_style):
+    """Test that data_ and aria_ special props are correctly added to the component.
+
+    Args:
+        component_kwargs: The component kwargs.
+        exp_custom_attrs: The expected custom attributes.
+        exp_style: The expected style.
+    """
+    component = SpecialComponent.create(**component_kwargs)
+    assert component.custom_attrs == exp_custom_attrs
+    assert component.style == exp_style
+    for prop in SpecialComponent.get_props():
+        if prop in component_kwargs:
+            assert getattr(component, prop)._var_value == component_kwargs[prop]