From 94e8c819c3d4dfc710ae70f5c842f457d0ae68ad Mon Sep 17 00:00:00 2001 From: Gautam Manchandani Date: Wed, 8 Apr 2026 15:17:30 +0530 Subject: [PATCH 01/12] Support TypedDict form submit data Signed-off-by: Gautam Manchandani --- .../src/reflex_base/event/__init__.py | 48 +++- .../el/elements/forms.py | 269 +++++++++++++++++- pyi_hashes.json | 2 +- tests/units/components/forms/test_form.py | 157 ++++++++++ 4 files changed, 461 insertions(+), 15 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/event/__init__.py b/packages/reflex-base/src/reflex_base/event/__init__.py index ca0347745f9..818be84710f 100644 --- a/packages/reflex-base/src/reflex_base/event/__init__.py +++ b/packages/reflex-base/src/reflex_base/event/__init__.py @@ -23,7 +23,14 @@ overload, ) -from typing_extensions import Self, TypeAliasType, TypedDict, TypeVarTuple, Unpack +from typing_extensions import ( + Self, + TypeAliasType, + TypedDict, + TypeVarTuple, + Unpack, + is_typeddict, +) from reflex_base import constants from reflex_base.components.field import BaseField @@ -58,6 +65,10 @@ if TYPE_CHECKING: from reflex.state import BaseState + BASE_STATE = TypeVar("BASE_STATE", bound=BaseState) +else: + BASE_STATE = TypeVar("BASE_STATE") + @dataclasses.dataclass( init=True, @@ -1683,6 +1694,28 @@ def _values_returned_from_event(event_spec_annotations: list[Any]) -> list[Any]: ] +def _is_mapping_style_event_arg_compatible_with_typed_dict( + provided_event_arg_type: Any, + callback_param_type: Any, +) -> bool: + """Check whether a mapping-style event payload can satisfy a TypedDict callback. + + This keeps the compatibility relaxation narrow to dict-like event payloads, such + as form submission data, without weakening unrelated event type checks. + + Args: + provided_event_arg_type: The type produced by the event trigger. + callback_param_type: The callback parameter annotation. + + Returns: + Whether the provided event payload should be treated as compatible. + """ + return is_typeddict(callback_param_type) and safe_issubclass( + get_origin(provided_event_arg_type) or provided_event_arg_type, + Mapping, + ) + + def _check_event_args_subclass_of_callback( callback_params_names: list[str], provided_event_types: list[Any], @@ -1724,15 +1757,18 @@ def _check_event_args_subclass_of_callback( continue type_match_found.setdefault(arg, False) + callback_param_type = callback_param_name_to_type[arg] try: compare_result = typehint_issubclass( - args_types_without_vars[i], callback_param_name_to_type[arg] + args_types_without_vars[i], callback_param_type + ) or _is_mapping_style_event_arg_compatible_with_typed_dict( + args_types_without_vars[i], callback_param_type ) except TypeError as te: callback_name_context = f" of {callback_name}" if callback_name else "" key_context = f" for {key}" if key else "" - msg = f"Could not compare types {args_types_without_vars[i]} and {callback_param_name_to_type[arg]} for argument {arg}{callback_name_context}{key_context}." + msg = f"Could not compare types {args_types_without_vars[i]} and {callback_param_type} for argument {arg}{callback_name_context}{key_context}." raise TypeError(msg) from te if compare_result: @@ -1744,7 +1780,7 @@ def _check_event_args_subclass_of_callback( ) delayed_exceptions.append( EventHandlerArgTypeMismatchError( - f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {callback_param_name_to_type[arg]}{as_annotated_in} instead." + f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {callback_param_type}{as_annotated_in} instead." ) ) @@ -2557,10 +2593,6 @@ def __call__(self, *args: Var) -> Any: if TYPE_CHECKING: from reflex.state import BaseState - BASE_STATE = TypeVar("BASE_STATE", bound=BaseState) -else: - BASE_STATE = TypeVar("BASE_STATE") - class EventNamespace: """A namespace for event related classes.""" diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index 8a2422081a5..da73dbe5774 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -2,17 +2,19 @@ from __future__ import annotations -from collections.abc import Iterator +from collections.abc import Iterator, Mapping +from functools import partial from hashlib import md5 -from typing import Any, ClassVar, Literal +from typing import Any, ClassVar, Literal, TypeVar, get_type_hints -from reflex_base.components.component import field +from reflex_base.components.component import BaseComponent, Component, field from reflex_base.components.tags.tag import Tag from reflex_base.constants import Dirs, EventTriggers from reflex_base.event import ( FORM_DATA, EventChain, EventHandler, + EventSpec, checked_input_event, float_input_event, input_event, @@ -21,16 +23,34 @@ on_submit_event, on_submit_string_event, prevent_default, + unwrap_var_annotation, ) +from reflex_base.utils.exceptions import EventHandlerValueError from reflex_base.utils.imports import ImportDict from reflex_base.vars import VarData from reflex_base.vars.base import LiteralVar, Var from reflex_base.vars.number import ternary_operation +from typing_extensions import is_typeddict from reflex_components_core.el.element import Element from .base import BaseHTML +_DYNAMIC_FORM_FIELD = object() + +_KNOWN_SUBMIT_CONTROL_TYPES = { + "reflex_components_radix.primitives.slider.SliderRoot", + "reflex_components_radix.themes.components.checkbox.Checkbox", + "reflex_components_radix.themes.components.checkbox_group.CheckboxGroupRoot", + "reflex_components_radix.themes.components.radio_cards.RadioCardsRoot", + "reflex_components_radix.themes.components.radio_group.RadioGroupRoot", + "reflex_components_radix.themes.components.select.SelectRoot", + "reflex_components_radix.themes.components.slider.Slider", + "reflex_components_radix.themes.components.switch.Switch", +} + +FORM_SUBMIT_MAPPING = TypeVar("FORM_SUBMIT_MAPPING", bound=Mapping[str, Any]) + def _handle_submit_js_template( handle_submit_unique_name: str, @@ -66,6 +86,165 @@ def _handle_submit_js_template( """ +def on_submit_mapping_event( + form_data: Var[FORM_SUBMIT_MAPPING], +) -> tuple[Var[FORM_SUBMIT_MAPPING]]: + """Provide a generic mapping-style submit event spec for type checkers. + + Args: + form_data: The form submission payload. + + Returns: + The form data payload. + """ + return (form_data,) + + +def _iter_form_components(component: BaseComponent) -> Iterator[BaseComponent]: + """Yield a component and all nested components that may contribute form data. + + Args: + component: The component to walk. + + Yields: + The component and its nested component descendants. + """ + yield component + for child in component.children: + if isinstance(child, BaseComponent): + yield from _iter_form_components(child) + if isinstance(component, Component): + for component_in_props in component._get_components_in_props(): + yield from _iter_form_components(component_in_props) + + +def _get_static_string_prop( + component: BaseComponent, + prop_name: str, +) -> str | object | None: + """Resolve a component prop when it is statically known to be a string. + + Args: + component: The component being inspected. + prop_name: The prop to resolve. + + Returns: + The resolved string, ``_DYNAMIC_FORM_FIELD`` for dynamic vars, or ``None``. + """ + value = getattr(component, prop_name, None) + if value is None: + return None + if isinstance(value, str): + return value + if isinstance(value, LiteralVar): + decoded = value._decode() + if isinstance(decoded, str): + return decoded + return None + if isinstance(value, Var): + return _DYNAMIC_FORM_FIELD + return None + + +def _is_submit_participating_control(component: BaseComponent) -> bool: + """Check whether a component can contribute a named field to form data. + + Args: + component: The component to inspect. + + Returns: + Whether the component is a submit-participating control. + """ + if isinstance(component, (BaseInput, Select, Textarea)): + return True + component_type_name = ( + f"{component.__class__.__module__}.{component.__class__.__name__}" + ) + return component_type_name in _KNOWN_SUBMIT_CONTROL_TYPES + + +def _is_form_data_payload_arg(value: Var) -> bool: + """Check whether an event arg value is the form submission payload. + + Args: + value: The event arg value. + + Returns: + Whether the arg is the ``form_data`` payload. + """ + return isinstance(value, Var) and value._js_expr == FORM_DATA._js_expr + + +def _get_handler_name(handler: EventHandler) -> str: + """Get a stable fully qualified handler name for errors. + + Args: + handler: The handler to name. + + Returns: + The fully qualified handler name. + """ + return handler.fn.__qualname__ + + +def _resolve_on_submit_typed_dict_contract( + event_spec: EventSpec, +) -> tuple[str, type[Any], frozenset[str]] | None: + """Resolve the TypedDict contract for an on_submit handler, if any. + + Args: + event_spec: The finalized event spec in the on_submit chain. + + Returns: + The handler name, TypedDict annotation, and required keys, or ``None``. + """ + form_data_param_name = next( + ( + param._js_expr + for param, value in event_spec.args + if _is_form_data_payload_arg(value) + ), + None, + ) + if form_data_param_name is None: + return None + + func = ( + event_spec.handler.fn.func + if isinstance(event_spec.handler.fn, partial) + else event_spec.handler.fn + ) + try: + type_hints = get_type_hints(func) + except NameError: + return None + + annotation = type_hints.get(form_data_param_name) + if annotation is None: + return None + + annotation = unwrap_var_annotation(annotation) + if not is_typeddict(annotation): + return None + + required_fields = getattr(annotation, "__required_keys__", frozenset()) + return _get_handler_name(event_spec.handler), annotation, required_fields + + +def _format_field_list(fields: tuple[str, ...]) -> str: + """Format field names as a bullet list. + + Args: + fields: The fields to format. + + Returns: + A human-readable bullet list. + """ + if not fields: + return ' - "(none)"' + return "\n".join(f' - "{field}"' for field in fields) + + ButtonType = Literal["submit", "reset", "button"] @@ -172,9 +351,9 @@ class Form(BaseHTML): doc="The name used to make this form's submit handler function unique." ) - on_submit: EventHandler[on_submit_event, on_submit_string_event] = field( - doc="Fired when the form is submitted" - ) + on_submit: EventHandler[ + on_submit_event, on_submit_mapping_event, on_submit_string_event + ] = field(doc="Fired when the form is submitted") @classmethod def create(cls, *children, **props): @@ -196,6 +375,7 @@ def create(cls, *children, **props): # Render the form hooks and use the hash of the resulting code to create a unique name. props["handle_submit_unique_name"] = "" form = super().create(*children, **props) + form._validate_on_submit_typed_dict_fields() # pyright: ignore[reportAttributeAccessIssue] form.handle_submit_unique_name = md5( # pyright: ignore[reportAttributeAccessIssue] str(form._get_all_hooks()).encode("utf-8") ).hexdigest() @@ -263,6 +443,83 @@ def _get_form_refs(self) -> dict[str, Any]: ) return form_refs + def _get_static_form_field_keys(self) -> tuple[set[str], bool]: + """Collect statically known form-data keys and whether any are dynamic. + + Returns: + The known keys and whether any name/id identifiers are dynamic. + """ + form_keys = set(self._get_form_refs()) + has_dynamic_identifiers = False + + for component in _iter_form_components(self): + if component is self or not _is_submit_participating_control(component): + continue + + name = _get_static_string_prop(component, "name") + if name is _DYNAMIC_FORM_FIELD: + has_dynamic_identifiers = True + elif isinstance(name, str): + form_keys.add(name) + + if _get_static_string_prop(component, "id") is _DYNAMIC_FORM_FIELD: + has_dynamic_identifiers = True + + return form_keys, has_dynamic_identifiers + + def _validate_on_submit_typed_dict_fields(self) -> None: + """Validate statically knowable form fields against TypedDict submit handlers. + + Raises: + EventHandlerValueError: If a required TypedDict field is missing. + """ + on_submit = self.event_triggers.get(EventTriggers.ON_SUBMIT) + if not isinstance(on_submit, EventChain): + return + + if any(not isinstance(event, EventSpec) for event in on_submit.events): + return + + event_specs = tuple( + event for event in on_submit.events if isinstance(event, EventSpec) + ) + typed_dict_contracts = [ + contract + for event in event_specs + if (contract := _resolve_on_submit_typed_dict_contract(event)) is not None + ] + if not typed_dict_contracts: + return + + form_keys, has_dynamic_identifiers = self._get_static_form_field_keys() + for handler_name, typed_dict_type, required_fields in typed_dict_contracts: + required_field_names = tuple(sorted(required_fields)) + if not required_field_names: + continue + + missing_fields = tuple( + field for field in required_field_names if field not in form_keys + ) + if not missing_fields or has_dynamic_identifiers: + continue + + present_fields = tuple( + field for field in required_field_names if field in form_keys + ) + msg = ( + f"Form field mismatch for on_submit handler `{handler_name}`.\n\n" + f"The handler expects form data matching `{typed_dict_type.__name__}` " + "with required fields:\n" + f"{_format_field_list(required_field_names)}\n\n" + "Fields missing from the form:\n" + f"{_format_field_list(missing_fields)}\n\n" + "Matching fields present in the form:\n" + f"{_format_field_list(present_fields)}\n\n" + "Hint: Add controls with matching static `name` or `id` values, or " + "make the TypedDict fields optional." + ) + raise EventHandlerValueError(msg) + def _get_vars( self, include_children: bool = True, ignore_ids: set[int] | None = None ) -> Iterator[Var]: diff --git a/pyi_hashes.json b/pyi_hashes.json index f7900836abd..ac6cf3269fe 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -27,7 +27,7 @@ "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "1a8824cdd243efc876157b97f9f1b714", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "e6c845f2f29eb079697a2e31b0c2f23a", "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7c74980207dc1a5cac14083f2edd31ba", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "da7ef00fd67699eeeb55e33279c2eb8d", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "d7f9b70a1a1d7f123a0e79c6c1534ea8", "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "0ea0058ea7b6ae03138c7c85df963c32", "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "97f7f6c66533bb3947a43ceefe160d49", "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "7ea09671a42d75234a0464fc3601577c", diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index f3ea3c03228..6f2d61e5a64 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -1,6 +1,15 @@ +from typing import TypedDict + +import pytest from reflex_base.event import EventChain, prevent_default +from reflex_base.utils.exceptions import EventHandlerValueError from reflex_base.vars.base import Var +from reflex_components_core.el.elements.forms import Form as HTMLForm +from reflex_components_core.el.elements.forms import Input from reflex_components_radix.primitives.form import Form +from typing_extensions import NotRequired + +import reflex as rx def test_render_on_submit(): @@ -20,3 +29,151 @@ def test_render_no_on_submit(): assert isinstance(f.event_triggers["on_submit"], EventChain) assert len(f.event_triggers["on_submit"].events) == 1 assert f.event_triggers["on_submit"].events[0] == prevent_default + + +@pytest.mark.parametrize("form_factory", [HTMLForm.create, Form.create]) +def test_on_submit_accepts_typed_dict_form_data(form_factory): + """TypedDict-annotated submit handlers should be accepted.""" + + class SignupData(TypedDict): + name: str + email: str + + class SignupState(rx.State): + @rx.event + def on_submit(self, form_data: SignupData): + pass + + form = form_factory( + Input.create(name="name"), + Input.create(name="email"), + on_submit=SignupState.on_submit, + ) + + assert isinstance(form.event_triggers["on_submit"], EventChain) + + +def test_on_submit_accepts_id_backed_typed_dict_form_data(): + """Static ids that are mirrored into form_data should satisfy TypedDict keys.""" + + class SignupData(TypedDict): + email_input: str + + class SignupState(rx.State): + @rx.event + def on_submit(self, form_data: SignupData): + pass + + form = HTMLForm.create( + Input.create(id="email_input"), + on_submit=SignupState.on_submit, + ) + + assert isinstance(form.event_triggers["on_submit"], EventChain) + + +def test_on_submit_accepts_typed_dict_with_optional_fields(): + """Optional TypedDict keys should not be required in the form.""" + + class SignupData(TypedDict): + email: str + nickname: NotRequired[str] + + class SignupState(rx.State): + @rx.event + def on_submit(self, form_data: SignupData): + pass + + form = HTMLForm.create( + Input.create(name="email"), + on_submit=SignupState.on_submit, + ) + + assert isinstance(form.event_triggers["on_submit"], EventChain) + + +def test_on_submit_allows_extra_typed_dict_form_fields(): + """Forms may include more fields than the TypedDict requires.""" + + class SignupData(TypedDict): + email: str + + class SignupState(rx.State): + @rx.event + def on_submit(self, form_data: SignupData): + pass + + form = HTMLForm.create( + Input.create(name="email"), + Input.create(name="nickname"), + on_submit=SignupState.on_submit, + ) + + assert isinstance(form.event_triggers["on_submit"], EventChain) + + +def test_on_submit_resolves_typed_dict_after_bound_args(): + """The final submit payload parameter should still resolve after binding args.""" + + class SignupData(TypedDict): + email: str + + class SignupState(rx.State): + @rx.event + def on_submit(self, source: str, form_data: SignupData): + pass + + form = HTMLForm.create( + Input.create(name="email"), + on_submit=SignupState.on_submit("marketing"), # pyright: ignore [reportCallIssue] + ) + + assert isinstance(form.event_triggers["on_submit"], EventChain) + + +def test_on_submit_typed_dict_missing_fields_raises_helpful_error(): + """Missing required TypedDict keys should produce a focused compile-time error.""" + + class SignupData(TypedDict): + fname: str + lname: str + email: str + + class SignupState(rx.State): + @rx.event + def on_submit(self, form_data: SignupData): + pass + + with pytest.raises(EventHandlerValueError) as err: + HTMLForm.create( + Input.create(name="email"), + on_submit=SignupState.on_submit, + ) + + error = str(err.value) + assert "Form field mismatch for on_submit handler" in error + assert "SignupState.on_submit" in error + assert "SignupData" in error + assert '"fname"' in error + assert '"lname"' in error + assert '"email"' in error + assert "Matching fields present in the form" in error + + +def test_on_submit_typed_dict_skips_dynamic_field_identifiers(): + """Dynamic field names should skip strict validation instead of raising.""" + + class SignupData(TypedDict): + email: str + + class SignupState(rx.State): + @rx.event + def on_submit(self, form_data: SignupData): + pass + + form = HTMLForm.create( + Input.create(name=Var(_js_expr="dynamic_name", _var_type=str)), + on_submit=SignupState.on_submit, + ) + + assert isinstance(form.event_triggers["on_submit"], EventChain) From eb8b5fecd9aba9b5f2f068bd6c9e56d828996459 Mon Sep 17 00:00:00 2001 From: Gautam Manchandani Date: Wed, 8 Apr 2026 17:01:22 +0530 Subject: [PATCH 02/12] Fix TypedDict form CI followups --- .../src/reflex_base/event/__init__.py | 29 ++++++++---- .../el/elements/forms.py | 47 +++++++++++++++++-- pyi_hashes.json | 2 +- tests/units/components/test_component.py | 30 +++++++++++- 4 files changed, 93 insertions(+), 15 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/event/__init__.py b/packages/reflex-base/src/reflex_base/event/__init__.py index 818be84710f..3cd37b65e3e 100644 --- a/packages/reflex-base/src/reflex_base/event/__init__.py +++ b/packages/reflex-base/src/reflex_base/event/__init__.py @@ -1694,26 +1694,35 @@ def _values_returned_from_event(event_spec_annotations: list[Any]) -> list[Any]: ] -def _is_mapping_style_event_arg_compatible_with_typed_dict( +def _is_on_submit_mapping_event_arg_compatible_with_typed_dict( provided_event_arg_type: Any, callback_param_type: Any, + key: str, ) -> bool: - """Check whether a mapping-style event payload can satisfy a TypedDict callback. + """Check whether an on_submit mapping payload can satisfy a TypedDict callback. - This keeps the compatibility relaxation narrow to dict-like event payloads, such - as form submission data, without weakening unrelated event type checks. + This keeps the compatibility relaxation scoped to form submission payloads + rather than applying to unrelated mapping-based event triggers. Args: provided_event_arg_type: The type produced by the event trigger. callback_param_type: The callback parameter annotation. + key: The event trigger key being validated. Returns: Whether the provided event payload should be treated as compatible. """ - return is_typeddict(callback_param_type) and safe_issubclass( - get_origin(provided_event_arg_type) or provided_event_arg_type, - Mapping, - ) + if key != constants.EventTriggers.ON_SUBMIT or not is_typeddict( + callback_param_type + ): + return False + + mapping_type = get_origin(provided_event_arg_type) or provided_event_arg_type + if not safe_issubclass(mapping_type, Mapping): + return False + + key_type = get_args(provided_event_arg_type)[:1] + return not key_type or typehint_issubclass(key_type[0], str) def _check_event_args_subclass_of_callback( @@ -1762,8 +1771,8 @@ def _check_event_args_subclass_of_callback( try: compare_result = typehint_issubclass( args_types_without_vars[i], callback_param_type - ) or _is_mapping_style_event_arg_compatible_with_typed_dict( - args_types_without_vars[i], callback_param_type + ) or _is_on_submit_mapping_event_arg_compatible_with_typed_dict( + args_types_without_vars[i], callback_param_type, key ) except TypeError as te: callback_name_context = f" of {callback_name}" if callback_name else "" diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index da73dbe5774..f460363e403 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -5,7 +5,7 @@ from collections.abc import Iterator, Mapping from functools import partial from hashlib import md5 -from typing import Any, ClassVar, Literal, TypeVar, get_type_hints +from typing import Any, ClassVar, Literal, TypeVar, get_origin, get_type_hints from reflex_base.components.component import BaseComponent, Component, field from reflex_base.components.tags.tag import Tag @@ -216,7 +216,7 @@ def _resolve_on_submit_typed_dict_contract( ) try: type_hints = get_type_hints(func) - except NameError: + except Exception: return None annotation = type_hints.get(form_data_param_name) @@ -227,10 +227,51 @@ def _resolve_on_submit_typed_dict_contract( if not is_typeddict(annotation): return None - required_fields = getattr(annotation, "__required_keys__", frozenset()) + required_fields = _get_required_typed_dict_fields(annotation) return _get_handler_name(event_spec.handler), annotation, required_fields +def _get_required_typed_dict_fields(typed_dict_type: type[Any]) -> frozenset[str]: + """Resolve required TypedDict keys in a cross-version-safe way. + + Args: + typed_dict_type: The TypedDict class to inspect. + + Returns: + The required field names for the TypedDict. + """ + try: + field_type_hints = get_type_hints(typed_dict_type, include_extras=True) + except Exception: + field_type_hints = getattr(typed_dict_type, "__annotations__", {}) + + total = getattr(typed_dict_type, "__total__", True) + required_fields = { + field_name + for field_name, annotation in field_type_hints.items() + if _is_required_typed_dict_field(annotation, total=total) + } + return frozenset(required_fields) + + +def _is_required_typed_dict_field(annotation: Any, *, total: bool) -> bool: + """Check whether a TypedDict field annotation is required. + + Args: + annotation: The field annotation to inspect. + total: Whether the TypedDict defaults to required fields. + + Returns: + Whether the field is required. + """ + marker_name = getattr(get_origin(annotation), "__name__", None) + if marker_name == "NotRequired": + return False + if marker_name == "Required": + return True + return total + + def _format_field_list(fields: tuple[str, ...]) -> str: """Format field names as a bullet list. diff --git a/pyi_hashes.json b/pyi_hashes.json index ac6cf3269fe..c3538f3de19 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -27,7 +27,7 @@ "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "1a8824cdd243efc876157b97f9f1b714", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "e6c845f2f29eb079697a2e31b0c2f23a", "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7c74980207dc1a5cac14083f2edd31ba", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "d7f9b70a1a1d7f123a0e79c6c1534ea8", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "1ff521334b753d334dbfbe309a8e2327", "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "0ea0058ea7b6ae03138c7c85df963c32", "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "97f7f6c66533bb3947a43ceefe160d49", "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "7ea09671a42d75234a0464fc3601577c", diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 5b3e601f176..0323bd6f7d2 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -1,6 +1,6 @@ from contextlib import nullcontext from dataclasses import dataclass -from typing import Any, ClassVar +from typing import Any, ClassVar, TypedDict import pytest from reflex_base.components.component import ( @@ -839,6 +839,34 @@ def get_event_triggers(cls) -> dict[str, Any]: C1.create(on_foo=C1State.mock_handler) +def test_non_submit_mapping_events_do_not_accept_typed_dict_handlers(): + """TypedDict relaxation should stay scoped to form submission handlers.""" + + class Payload(TypedDict): + email: str + + class C1State(BaseState): + def mock_handler(self, payload: Payload): + """Mock handler.""" + + def on_foo_spec(payload: Var[dict[str, int]]) -> tuple[Var[dict[str, int]]]: + return (payload,) + + class C1(Component): + library = "/local" + tag = "C1" + + @classmethod + def get_event_triggers(cls) -> dict[str, Any]: + return { + **super().get_event_triggers(), + "on_foo": on_foo_spec, + } + + with pytest.raises(EventHandlerArgTypeMismatchError): + C1.create(on_foo=C1State.mock_handler) + + def test_create_custom_component(my_component): """Test that we can create a custom component. From 8d0d0da18240f2e83a8cb2125b7ddde4ff671914 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Thu, 16 Apr 2026 16:13:46 +0500 Subject: [PATCH 03/12] Fix TypedDict form validation edge cases and code quality Use __required_keys__ for inherited TypedDict optional fields, skip validation for forms with id (HTML form attribute), replace stringly-typed control detection with _is_form_control marker, use concrete Mapping type in event spec, narrow exception handling, and add integration tests. --- .../src/reflex_base/components/component.py | 3 + .../src/reflex_base/event/__init__.py | 15 +- .../src/reflex_base/utils/pyi_generator.py | 1 + .../el/elements/forms.py | 206 +++++----------- .../primitives/slider.py | 1 + .../themes/components/checkbox.py | 1 + .../themes/components/checkbox_group.py | 1 + .../themes/components/radio_cards.py | 1 + .../themes/components/radio_group.py | 1 + .../themes/components/select.py | 1 + .../themes/components/slider.py | 1 + .../themes/components/switch.py | 1 + pyi_hashes.json | 214 ++++++++--------- .../integration/test_typeddict_form_submit.py | 222 ++++++++++++++++++ tests/units/components/forms/test_form.py | 42 ++++ 15 files changed, 443 insertions(+), 268 deletions(-) create mode 100644 tests/integration/test_typeddict_form_submit.py diff --git a/packages/reflex-base/src/reflex_base/components/component.py b/packages/reflex-base/src/reflex_base/components/component.py index d7903c5c9e2..e4f58fbf1b0 100644 --- a/packages/reflex-base/src/reflex_base/components/component.py +++ b/packages/reflex-base/src/reflex_base/components/component.py @@ -701,6 +701,9 @@ class Component(BaseComponent, ABC): # props to change the name of _rename_props: ClassVar[dict[str, str]] = {} + # Whether this component contributes a named field to form submission data. + _is_form_control: ClassVar[bool] = False + custom_attrs: dict[str, Var | Any] = field( doc="custom attribute", default_factory=dict, is_javascript_property=False ) diff --git a/packages/reflex-base/src/reflex_base/event/__init__.py b/packages/reflex-base/src/reflex_base/event/__init__.py index 3cd37b65e3e..44cead54418 100644 --- a/packages/reflex-base/src/reflex_base/event/__init__.py +++ b/packages/reflex-base/src/reflex_base/event/__init__.py @@ -66,8 +66,6 @@ from reflex.state import BaseState BASE_STATE = TypeVar("BASE_STATE", bound=BaseState) -else: - BASE_STATE = TypeVar("BASE_STATE") @dataclasses.dataclass( @@ -825,6 +823,7 @@ def checked_input_event(e: ObjectVar[JavascriptInputEvent]) -> tuple[Var[bool]]: FORM_DATA = Var(_js_expr="form_data") +FORM_SUBMIT_MAPPING = TypeVar("FORM_SUBMIT_MAPPING", bound=Mapping[str, Any]) def on_submit_event() -> tuple[Var[dict[str, Any]]]: @@ -2648,6 +2647,7 @@ class EventNamespace: EVENT_ACTIONS_MARKER = EVENT_ACTIONS_MARKER _EVENT_FIELDS = _EVENT_FIELDS FORM_DATA = FORM_DATA + FORM_SUBMIT_MAPPING = FORM_SUBMIT_MAPPING upload_files = upload_files upload_files_chunk = upload_files_chunk stop_propagation = stop_propagation @@ -2681,7 +2681,7 @@ def __new__( @overload def __new__( cls, - func: Callable[[BASE_STATE, Unpack[P]], Any], + func: "Callable[[BASE_STATE, Unpack[P]], Any]", *, background: bool | None = None, stop_propagation: bool | None = None, @@ -2693,7 +2693,7 @@ def __new__( def __new__( cls, - func: Callable[[BASE_STATE, Unpack[P]], Any] | None = None, + func: "Callable[[BASE_STATE, Unpack[P]], Any] | None" = None, *, background: bool | None = None, stop_propagation: bool | None = None, @@ -2701,10 +2701,7 @@ def __new__( throttle: int | None = None, debounce: int | None = None, temporal: bool | None = None, - ) -> ( - EventCallback[Unpack[P]] - | Callable[[Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]]] - ): + ) -> "EventCallback[Unpack[P]] | Callable[[Callable[[BASE_STATE, Unpack[P]], Any]], EventCallback[Unpack[P]]]": """Wrap a function to be used as an event. Args: @@ -2752,7 +2749,7 @@ def _build_event_actions(): return event_actions def wrapper( - func: Callable[[BASE_STATE, Unpack[P]], T], + func: "Callable[[BASE_STATE, Unpack[P]], T]", ) -> EventCallback[Unpack[P]]: if background is True: if not inspect.iscoroutinefunction( diff --git a/packages/reflex-base/src/reflex_base/utils/pyi_generator.py b/packages/reflex-base/src/reflex_base/utils/pyi_generator.py index 3479f10f321..70b37bc230d 100644 --- a/packages/reflex-base/src/reflex_base/utils/pyi_generator.py +++ b/packages/reflex-base/src/reflex_base/utils/pyi_generator.py @@ -115,6 +115,7 @@ def _safe_issubclass(cls: Any, cls_check: Any | tuple[Any, ...]) -> bool: "EventHandler", "EventSpec", "EventType", + "FORM_SUBMIT_MAPPING", "KeyInputInfo", "PointerEventInfo", ], diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index f460363e403..16d30f9c42d 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -2,16 +2,17 @@ from __future__ import annotations -from collections.abc import Iterator, Mapping +from collections.abc import Iterator from functools import partial from hashlib import md5 -from typing import Any, ClassVar, Literal, TypeVar, get_origin, get_type_hints +from typing import Any, ClassVar, Literal, get_type_hints from reflex_base.components.component import BaseComponent, Component, field from reflex_base.components.tags.tag import Tag from reflex_base.constants import Dirs, EventTriggers from reflex_base.event import ( FORM_DATA, + FORM_SUBMIT_MAPPING, EventChain, EventHandler, EventSpec, @@ -38,19 +39,6 @@ _DYNAMIC_FORM_FIELD = object() -_KNOWN_SUBMIT_CONTROL_TYPES = { - "reflex_components_radix.primitives.slider.SliderRoot", - "reflex_components_radix.themes.components.checkbox.Checkbox", - "reflex_components_radix.themes.components.checkbox_group.CheckboxGroupRoot", - "reflex_components_radix.themes.components.radio_cards.RadioCardsRoot", - "reflex_components_radix.themes.components.radio_group.RadioGroupRoot", - "reflex_components_radix.themes.components.select.SelectRoot", - "reflex_components_radix.themes.components.slider.Slider", - "reflex_components_radix.themes.components.switch.Switch", -} - -FORM_SUBMIT_MAPPING = TypeVar("FORM_SUBMIT_MAPPING", bound=Mapping[str, Any]) - def _handle_submit_js_template( handle_submit_unique_name: str, @@ -146,132 +134,6 @@ def _get_static_string_prop( return None -def _is_submit_participating_control(component: BaseComponent) -> bool: - """Check whether a component can contribute a named field to form data. - - Args: - component: The component to inspect. - - Returns: - Whether the component is a submit-participating control. - """ - if isinstance(component, (BaseInput, Select, Textarea)): - return True - component_type_name = ( - f"{component.__class__.__module__}.{component.__class__.__name__}" - ) - return component_type_name in _KNOWN_SUBMIT_CONTROL_TYPES - - -def _is_form_data_payload_arg(value: Var) -> bool: - """Check whether an event arg value is the form submission payload. - - Args: - value: The event arg value. - - Returns: - Whether the arg is the ``form_data`` payload. - """ - return isinstance(value, Var) and value._js_expr == FORM_DATA._js_expr - - -def _get_handler_name(handler: EventHandler) -> str: - """Get a stable fully qualified handler name for errors. - - Args: - handler: The handler to name. - - Returns: - The fully qualified handler name. - """ - return handler.fn.__qualname__ - - -def _resolve_on_submit_typed_dict_contract( - event_spec: EventSpec, -) -> tuple[str, type[Any], frozenset[str]] | None: - """Resolve the TypedDict contract for an on_submit handler, if any. - - Args: - event_spec: The finalized event spec in the on_submit chain. - - Returns: - The handler name, TypedDict annotation, and required keys, or ``None``. - """ - form_data_param_name = next( - ( - param._js_expr - for param, value in event_spec.args - if _is_form_data_payload_arg(value) - ), - None, - ) - if form_data_param_name is None: - return None - - func = ( - event_spec.handler.fn.func - if isinstance(event_spec.handler.fn, partial) - else event_spec.handler.fn - ) - try: - type_hints = get_type_hints(func) - except Exception: - return None - - annotation = type_hints.get(form_data_param_name) - if annotation is None: - return None - - annotation = unwrap_var_annotation(annotation) - if not is_typeddict(annotation): - return None - - required_fields = _get_required_typed_dict_fields(annotation) - return _get_handler_name(event_spec.handler), annotation, required_fields - - -def _get_required_typed_dict_fields(typed_dict_type: type[Any]) -> frozenset[str]: - """Resolve required TypedDict keys in a cross-version-safe way. - - Args: - typed_dict_type: The TypedDict class to inspect. - - Returns: - The required field names for the TypedDict. - """ - try: - field_type_hints = get_type_hints(typed_dict_type, include_extras=True) - except Exception: - field_type_hints = getattr(typed_dict_type, "__annotations__", {}) - - total = getattr(typed_dict_type, "__total__", True) - required_fields = { - field_name - for field_name, annotation in field_type_hints.items() - if _is_required_typed_dict_field(annotation, total=total) - } - return frozenset(required_fields) - - -def _is_required_typed_dict_field(annotation: Any, *, total: bool) -> bool: - """Check whether a TypedDict field annotation is required. - - Args: - annotation: The field annotation to inspect. - total: Whether the TypedDict defaults to required fields. - - Returns: - Whether the field is required. - """ - marker_name = getattr(get_origin(annotation), "__name__", None) - if marker_name == "NotRequired": - return False - if marker_name == "Required": - return True - return total - - def _format_field_list(fields: tuple[str, ...]) -> str: """Format field names as a bullet list. @@ -494,7 +356,7 @@ def _get_static_form_field_keys(self) -> tuple[set[str], bool]: has_dynamic_identifiers = False for component in _iter_form_components(self): - if component is self or not _is_submit_participating_control(component): + if component is self or not getattr(component, "_is_form_control", False): continue name = _get_static_string_prop(component, "name") @@ -518,21 +380,58 @@ def _validate_on_submit_typed_dict_fields(self) -> None: if not isinstance(on_submit, EventChain): return - if any(not isinstance(event, EventSpec) for event in on_submit.events): - return + typed_dict_contracts: list[tuple[str, type[Any], frozenset[str]]] = [] + for event in on_submit.events: + if not isinstance(event, EventSpec): + return + form_data_param_name = next( + ( + param._js_expr + for param, value in event.args + if isinstance(value, Var) and value._js_expr == FORM_DATA._js_expr + ), + None, + ) + if form_data_param_name is None: + continue + + func = ( + event.handler.fn.func + if isinstance(event.handler.fn, partial) + else event.handler.fn + ) + try: + type_hints = get_type_hints(func) + except (NameError, AttributeError, TypeError): + continue + + annotation = type_hints.get(form_data_param_name) + if annotation is None: + continue + + annotation = unwrap_var_annotation(annotation) + if not is_typeddict(annotation): + continue + + required_fields = frozenset( + getattr(annotation, "__required_keys__", frozenset()) + ) + typed_dict_contracts.append(( + event.handler.fn.__qualname__, + annotation, + required_fields, + )) - event_specs = tuple( - event for event in on_submit.events if isinstance(event, EventSpec) - ) - typed_dict_contracts = [ - contract - for event in event_specs - if (contract := _resolve_on_submit_typed_dict_contract(event)) is not None - ] if not typed_dict_contracts: return + # When the form has an id, external controls may be associated via the + # HTML ``form`` attribute so we cannot validate statically. + if _get_static_string_prop(self, "id") is not None: + return + form_keys, has_dynamic_identifiers = self._get_static_form_field_keys() + for handler_name, typed_dict_type, required_fields in typed_dict_contracts: required_field_names = tuple(sorted(required_fields)) if not required_field_names: @@ -607,6 +506,7 @@ class BaseInput(BaseHTML): """A base class for input elements.""" tag = "input" + _is_form_control = True accept: Var[str] = field(doc="Accepted types of files when the input is file type") @@ -884,6 +784,7 @@ class Select(BaseHTML): """Display the select element.""" tag = "select" + _is_form_control = True auto_complete: Var[str] = field( doc="Whether the form control should have autocomplete enabled" @@ -954,6 +855,7 @@ class Textarea(BaseHTML): """Display the textarea element.""" tag = "textarea" + _is_form_control = True auto_complete: Var[str] = field( doc="Whether the form control should have autocomplete enabled" diff --git a/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py b/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py index 68a4d5971bf..0e9e88474c7 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.py @@ -26,6 +26,7 @@ class SliderRoot(SliderComponent): tag = "Root" alias = "RadixSliderRoot" + _is_form_control = True default_value: Var[Sequence[int]] diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py index bad90762fe4..abd98b12425 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.py @@ -23,6 +23,7 @@ class Checkbox(RadixThemesComponent): """Selects a single value, typically for submission in a form.""" tag = "Checkbox" + _is_form_control = True as_child: Var[bool] = field( doc="Change the default rendered element for the one passed as a child, merging their props and behavior." diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py index 63fe0741361..b1bd87b7199 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.py @@ -15,6 +15,7 @@ class CheckboxGroupRoot(RadixThemesComponent): """Root element for a CheckboxGroup component.""" tag = "CheckboxGroup.Root" + _is_form_control = True size: Var[Responsive[Literal["1", "2", "3"]]] = field( doc="Use the size prop to control the checkbox size." diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py index c87c9d39944..f0dded4db20 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.py @@ -15,6 +15,7 @@ class RadioCardsRoot(RadixThemesComponent): """Root element for RadioCards component.""" tag = "RadioCards.Root" + _is_form_control = True as_child: Var[bool] = field( doc="Change the default rendered element for the one passed as a child, merging their props and behavior." diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py index bbe96e1a2cb..fbda10c17db 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.py @@ -29,6 +29,7 @@ class RadioGroupRoot(RadixThemesComponent): """A set of interactive radio buttons where only one can be selected at a time.""" tag = "RadioGroup.Root" + _is_form_control = True size: Var[Responsive[Literal["1", "2", "3"]]] = field( default=LiteralVar.create("2"), diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py index a959d0c3828..9b95c3eae42 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.py @@ -21,6 +21,7 @@ class SelectRoot(RadixThemesComponent): """Displays a list of options for the user to pick from, triggered by a button.""" tag = "Select.Root" + _is_form_control = True size: Var[Responsive[Literal["1", "2", "3"]]] = field( doc='The size of the select: "1" | "2" | "3"' diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py index e96690d0bc5..5e8e3d0597e 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.py @@ -23,6 +23,7 @@ class Slider(RadixThemesComponent): """Provides user selection from a range of values.""" tag = "Slider" + _is_form_control = True as_child: Var[bool] = field( doc="Change the default rendered element for the one passed as a child, merging their props and behavior." diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py index e379557c48a..6f598928419 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.py @@ -16,6 +16,7 @@ class Switch(RadixThemesComponent): """A toggle switch alternative to the checkbox.""" tag = "Switch" + _is_form_control = True as_child: Var[bool] = field( doc="Change the default rendered element for the one passed as a child, merging their props and behavior." diff --git a/pyi_hashes.json b/pyi_hashes.json index c3538f3de19..33e8f9b0c6c 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,124 +1,124 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "2797061144c4199f57848f6673a05a7f", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "db0de2879d57870831a030a69b5282b7", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "03a6445206663c5c3de5c77b943ae42d", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "6d0d409dca87762db4f00adda6c98669", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "82b29d23f2490161d42fd21021bd39c3", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "7009187aaaf191814d031e5462c48318", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "e7dfa98f5df5e30cb6d01d61b6974bef", - "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "0f98a7c1247e35059b76ae2985b7c81b", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "80a3090e5b7a46de6daa8e97e68e8638", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "f36f27e580041af842d348adbddcd600", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "39abed241f2def793dd0c59328bb0470", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "05d96de8a1d5f7be08de831b99663e67", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "b83e94900f988ef5d2fdf121b01be7fa", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "cfb0d5bcfe67f7c2b40868cdf3a5f7c1", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "8a69093c8d40b10b1f0b1c4e851e9d53", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "a361a12156aa65e053cc798678ccdfa8", + "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "bcc9c4f0b3088b34b7baa52356365271", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "8a8620e6837d7f9b6182711fd8d3a1d4", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "f5f0fce5f571af88e6aeb9e2acb9b74b", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "d63ceaa2beb41fed8f180c7c20cb2f9b", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "0c5eacde18185e690e6756bab2e13c59", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "7e56f60da63a68f6cc430c3ad34c91b9", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "466925a49ad56eca763fae214be03caa", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "60016e1eae752770bddb39361ff015fa", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "dd5142b3c9087bf2bf22651adf6f2724", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "29f5c106b98ddac94cf7c1244a02cfb1", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "9af2721b01868b24a48c7899ad6b1c69", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "20a3f4f500d44ac4365b6d831c6816ff", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "eb606cf8151e6769df7f2443ece739cd", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "5e28d554d2b4d7fae1ba35809c24f4fc", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "28bd59898f0402b33c34e14f3eef1282", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "4b34eca0e7338ec80ac5985345717bc9", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "6f3cdef9956dbe5c917edeefdffd1b0e", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "28e901ee970bec806ee766d0d126d739", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "dd8a55481eaa165e396d08a737a841eb", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "62acf1e2b977f76779742743b4e1debd", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "80624ed0df3475134478bc2e5094545e", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "8f089d215454cc8af2b52bb154dc3219", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "9c2cfc88c5081d7cdf4963fd3c79509c", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "eb84eb0bacdc3bb5381f724ff7098a7b", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "0a4776b3b0da5517962bb5cf3124c6c5", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "c0e70589f0038928eabd15e8f4ae91d4", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "98ce2d7f4747ac41062d7355c12a7c1a", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "c96fed4da42a13576d64f84e3c7cb25c", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "f09129ddefb57ab4c7769c86dc9a3153", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "1a8824cdd243efc876157b97f9f1b714", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "7304756e0a69059394a136bb6b2c70c2", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "e6c845f2f29eb079697a2e31b0c2f23a", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "7c74980207dc1a5cac14083f2edd31ba", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "1ff521334b753d334dbfbe309a8e2327", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "0ea0058ea7b6ae03138c7c85df963c32", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "97f7f6c66533bb3947a43ceefe160d49", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "7ea09671a42d75234a0464fc3601577c", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "869dca86b783149f9c59e1ae0d2900c1", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "c3a5a4f2d0594414a160fe59b13ccc26", - "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "b2acdc964feabe78154be141dc978555", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "e75fbe0454df06abf462ab579b698897", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "f88089a2f4270b981a28e385d07460b5", - "packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "c5ac8ba14fdce557063a832a79f43f68", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "e10210239ce7dc18980e70eec19b9353", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "2a93782c63e82a6939411273fe2486d9", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "f654cc9cb305712b485fcd676935c0c1", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "2d6efa2d5f2586a7036d606a24fb425d", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "ad4b084d94e50311f761d69b3173e357", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "241b80584f3e029145e6e003d1c476f2", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "9675fd9f996f25b5507fe28f94681d13", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "e0397b0e23a2b0787209309023002998", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "75a8156bd7fae17ed400f4aa00eff686", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "c84acdf6241f012faffa042b00095827", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "91cd0fedd809afa43dbd4ecce9448bf5", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "211128cc979178dbc67ef3763d0a86db", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "e7b3005bce566a5a940f576ad398c816", + "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "f7b21830f2583f77e6fb114e9175245a", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "08ff480b1865071d2984fe346919e2b6", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "d2df8254fc205746f377f01c618b5def", + "packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "411ed5fa8269441976d78759598de5da", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "90879e8fd9edc76c0c933c7be303de89", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "b1825d05d19baaca2126f01567a98aab", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "c545c02a893b8ad65784f366a0bdce33", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "db36cec6ad9b696f186e7a85b6d4eceb", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "3b8b6ce3878a38ef1c745e59ac5f128b", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "e6d95d7f73925d34887f48c7a5fdc996", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "b2f485bfde4978047b7b944cf15d92cb", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "5404a8da97e8b5129133d7f300e3f642", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "18ed34323f671fcf655639dc78d7c549", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "9c80e740d177b4a805dee3038d580941", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "b47313aefc9a740851ee332656446afd", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "d6a4f88f2988fa50fbed8a9026f5ef8b", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "00c0e0b6c8190f2db7fd847a25b5c03d", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "577ec9714a4d8bc9f7dd7eca22fe5252", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "bc69b9443d04ae7856c0a411a90755a9", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "ab1fcc6978810c0c3f93ab41d4b3d086", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "900fc82e6b90e3bd10fde20f94314c5e", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "0f6b97d2c5a779e1f0df103541d12092", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "b8f3b64dfb6b0bf49d5fc5d5efe565ab", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "50818b6f977013a55928df9066f8b160", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "c1ae974455eceaaa8476742e4a7fdff3", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "4abbecc560204625ba8d63e3d39541ad", "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "b433b9a099dc5b0ab008d02c85d38059", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "90a182a1444b73c006e52ea67c2b3db1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "3a419f78071b0dd6be55dc55e7334a1b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "299a1facb8ac556fcf704679d65c80b8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "fb9859c6cc85d8d479f84b0fa5902f20", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "f10f0169f81c78290333da831915762f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "2b8c68239c9e9646e71ef8e81d7b5f69", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "0f981ee0589f5501ab3c57e0aec01316", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "d30f1bfb42198177ea08d7d358e99339", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "c3bb335b309177ff03d2cadcaf623744", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "6a01812d601e8bf3dcd30dcccc75cb79", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "9b853e851805addacc2fcd995119f857", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "67a71ec6ed4945a9ce270bd51d40b94e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "0c975a4812efc267c87119f10880e1a9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "6425aae44ffe78f48699910906d16285", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "d0029ee04a971d8a51be0c99e414a139", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "1ee25c7dd27fece9881800226e322d6b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "924addbc155a178709f5fd38af4eb547", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "e315e9779663f2f2fc9c2ca322a5645f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "ec6cb8830971b2a04bebe7459c059b15", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "28384945a53620ad6075797f8ada7354", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "6a3a37bdc9136f8c19fb3a7f55e76d64", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "05cfece835e2660bbc1b096529dfdec0", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "3033070773e8e32de283ad917367b386", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "798eadec25895a56e36d23203a4e0444", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "f6140dbf7ad4c25595c6983dcacc2a60", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "e16ca79a2ad4c2919f56efb54830c1ef", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "473703616ed18d983dda3600899710a5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "12eb86d24886764bf1a5815e87ea519c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "6319f89d046b0fce8e9efb51e50dda9f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "c6da1db236da70dc40815a404d2e29b3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "d2dabb895d7fc63a556d3c3220e38b4d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "55b003f62cc3e5c85c90c82f8f595bc6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "c204f30612bfa35a62cb9f525a913f77", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "faeddfd0e3dc0e3bbcfdeaa6e42cb755", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "70f1d8fc55398d3cbb01f157c768419e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "a4c3052bc449924a630dad911f975e26", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "ec4e4ed03bd892c6f7d50ae4b490adb9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "06549c800759ae541cc3c3a74240af59", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "dcb6a8ff4668082fc9406579098abf87", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "69e4ce4eeaa60ac90ef120331cb8601c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "c35a5debc70e2e5029067a62e198eec6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "bc07be00fa24640bd46cf4697fb65ad5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "4f039c85361a40ff8432308333d174bd", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "05ede1daa54ef23711e27fdc962f2122", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "b3a074189f7297d8a1d6fba0400ccfcb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "1723869849f0f92c4d742f476b478666", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "09e3ba7d6e6a7b524c781a27435c0653", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "5f454887169d2c7a3c35274fb3a8fb8d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "8ad1259984e483a79b1dab34004c02fe", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "0e89c6352624f58722813e0d40f52fed", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "88ff2e3147c62fe01cd314222a4df7a8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "9a9992cdaae601669bcde6813dfbaaaa", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "b6f7c3554c9409aa20a60be52af50795", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "285c18153dc9027e27a318a85e6b66a6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "5f1a07370536b8495a0c0657cd9b07e8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "6a6ef283962fdff0a59df0e715069fec", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "99732b0338386e36bc007d4e972da76d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "01a420df97331567deea35a8fec0e2fa", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "7a80c426081c6c26f293d5c48872ff12", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "3249fcc42afa94504ba91e170d9bd0df", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "3132d260e7265b851cfe722cb24d5380", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "106075864fa2dd6d20ee06b6d8c0065d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "8e818b7a1702e2e347ced15921dcafb1", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "d73c673123481148d1fbf2fd17a508df", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "145d2e28aacb581ac163920729895366", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "228b915aa902337e06f83a9fefdf095a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "8d4c7928cb9e766616131af7aa055557", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "da395105458d21add8731d71ecadfb81", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "76177543a378cbc659e78ac79e9f7eeb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "cb0c2754036c7dd62ff686ae327f37c8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "f63f2ee2d7d0c43249ce9caf14d067bf", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "b1b26fb80f6efd50f68e8a4c22542b00", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "4f6c08f36ef1b07bb61ba6ef6e63bfd3", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "62ed931cdc3cbecda82973730c4d5ac8", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "7e78f23538ad93e7101372b973ebcf37", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "dcbb1dc8e860379188924c15dd21605b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "28e6cd3869c9cbad886b69b339e3ecf6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "004cae8160c3a91ae6c12b54205f5112", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "9dbe595eddc2ec731beeb3a98743be36", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "1fb9d0ce37de9c64f681ad70375b9e42", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "a729044bfe2d82404de07c4570262b55", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "74b017b63728ce328e110bc64f20a205", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "3a595ec7faf95645ab52bdad1bf9dc4a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "f3e44e291f3d96d06850d262de5d43a8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "a0a59ca93ea1e3a0e5136b9692a68d18", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "6ab750e790f0687b735d7464fa289c1f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "c0237feba5c808f10f0ac2a65f2c93c6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "f2ad9209f2ea21346206bec7e5fcf323", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "34a72530b228241b128f852d66a733ab", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "d96c741e5a7585b47cd221f4f797442f", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "c772ba7b2b631ffc51c8262504b03303", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "e537f6dd3565e29ea9481baef34f2be2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "6ebda83ce04060969d0e819d8f9ea43e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "6984bbeab128335148eef9a5d017366e", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "87f47ab58c33cf18d45dc13dea93d36a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "2afa52f3d8bb6a171f92b67a3c1cbad2", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "de7ee994f66a4c1d1a6ac2ad3370c30e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "3dd8bc1d7117b4e2b3b38438b4d6631a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "a71f56a8c51e9b00f953d87b16724bdb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "47a5f03dc4c85c473026069d23b6c531", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "ced137b2820a5e156cd1846ff113cfc9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "014444973b21272cf8c572b2913dfdf5", - "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "2c3c398ec0cc1476995f316cf8d0d271", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "9f8631e66d64f8bed90cbfd63615a97a", - "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "d0efeacb8b4162e9ace79f99c03e4368", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "1b071289b2f47735add7519331b53301", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "d3ad0524bebadd4a4aa88fa81b72e9a6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "61f99428ce61bd4969cf6066743d4521", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "8614a69c3697368b04eef70706d35fd0", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "deefd1590bdc2d42a6878972e4bbbe78", + "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "b6ea61f730b2ad16d9d4eaf5dab1f57c", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "d62abd377ce01f9369f84f4c387ae2d3", + "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "cd324d4dcd9c98774aca788e5cb8624b", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "7b8b69840a3637c1f1cac45ba815cccf", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "9e99f951112c86ec7991bc80985a76b1", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "5730b770af97f8c67d6d2d50e84fe14d", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "4097350ca05011733ce998898c6aefe7", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "db5298160144f23ae7abcaac68e845c7", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "75150b01510bdacf2c97fca347c86c59", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "dc43e142b089b1158588e999505444f6", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "5f4aa756dcb27f4c9d439f5067c33567", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "46f30814a6992ea8fef6b620892d0d38", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "099962a5883d861e8c294d97d727f85b", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "6d998f7745cf21548f873995536aa1d3", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "70c952c1f0f403d6b9fa5d8c04eb6802", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "2a83640d04139b66aa6ed493daaec336", "reflex/__init__.pyi": "5de3d4af8ea86e9755f622510b868196", "reflex/components/__init__.pyi": "f39a2af77f438fa243c58c965f19d42e", - "reflex/experimental/memo.pyi": "c10cbc554fe2ffdb3a008b59bc503936" + "reflex/experimental/memo.pyi": "76ffe267e664cd2778f08df30f78de7e" } diff --git a/tests/integration/test_typeddict_form_submit.py b/tests/integration/test_typeddict_form_submit.py new file mode 100644 index 00000000000..6233e768554 --- /dev/null +++ b/tests/integration/test_typeddict_form_submit.py @@ -0,0 +1,222 @@ +"""Integration tests for TypedDict-annotated form submissions.""" + +import asyncio +import functools +import json +from collections.abc import Generator + +import pytest +from reflex_base.utils import format +from selenium.webdriver.common.by import By + +from reflex.testing import AppHarness + + +def TypedDictFormSubmit(form_component): + """App with a form using a TypedDict-annotated on_submit handler. + + Args: + form_component: The str name of the form component to use. + """ + from typing import TypedDict + + from typing_extensions import NotRequired + + import reflex as rx + + class ContactData(TypedDict): + name: str + email: str + message: NotRequired[str] + + class FormState(rx.State): + form_data: rx.Field[dict] = rx.field(default_factory=dict) + + def form_submit(self, form_data: ContactData): + self.form_data = dict(form_data) + + app = rx.App() + + @app.add_page + def index(): + return rx.vstack( + rx.input( + value=FormState.router.session.client_token, + is_read_only=True, + id="token", + ), + eval(form_component)( + rx.vstack( + rx.input(name="name"), + rx.input(name="email"), + rx.text_area(name="message"), + rx.button("Submit", type_="submit"), + ), + on_submit=FormState.form_submit, + custom_attrs={"action": "/invalid"}, + ), + rx.text(FormState.form_data.to_string(), id="form-data"), + rx.spacer(), + height="100vh", + ) + + +def TypedDictInheritedFormSubmit(form_component): + """App with a form using an inherited TypedDict with optional parent fields. + + Args: + form_component: The str name of the form component to use. + """ + from typing import TypedDict + + import reflex as rx + + class BaseData(TypedDict, total=False): + nickname: str + + class SignupData(BaseData): + email: str + + class FormState(rx.State): + form_data: rx.Field[dict] = rx.field(default_factory=dict) + + def form_submit(self, form_data: SignupData): + self.form_data = dict(form_data) + + app = rx.App() + + @app.add_page + def index(): + return rx.vstack( + rx.input( + value=FormState.router.session.client_token, + is_read_only=True, + id="token", + ), + eval(form_component)( + rx.vstack( + rx.input(name="email"), + rx.input(name="nickname"), + rx.button("Submit", type_="submit"), + ), + on_submit=FormState.form_submit, + custom_attrs={"action": "/invalid"}, + ), + rx.text(FormState.form_data.to_string(), id="form-data"), + rx.spacer(), + height="100vh", + ) + + +@pytest.fixture( + scope="module", + params=[ + functools.partial(TypedDictFormSubmit, form_component="rx.form.root"), + functools.partial(TypedDictFormSubmit, form_component="rx.el.form"), + functools.partial(TypedDictInheritedFormSubmit, form_component="rx.el.form"), + ], + ids=[ + "typeddict-radix", + "typeddict-html", + "inherited-html", + ], +) +def typeddict_form(request, tmp_path_factory) -> Generator[AppHarness, None, None]: + """Start a TypedDict form app at tmp_path via AppHarness. + + Args: + request: pytest request fixture + tmp_path_factory: pytest tmp_path_factory fixture + + Yields: + running AppHarness instance + """ + param_id = request._pyfuncitem.callspec.id.replace("-", "_") + with AppHarness.create( + root=tmp_path_factory.mktemp("typeddict_form"), + app_source=request.param, + app_name=request.param.func.__name__ + f"_{param_id}", + ) as harness: + assert harness.app_instance is not None, "app is not running" + yield harness + + +@pytest.fixture +def driver(typeddict_form: AppHarness): + """Get an instance of the browser open to the app. + + Args: + typeddict_form: harness for the TypedDict form app + + Yields: + WebDriver instance. + """ + driver = typeddict_form.frontend() + try: + yield driver + finally: + driver.quit() + + +@pytest.mark.asyncio +async def test_typeddict_form_submit(driver, typeddict_form: AppHarness): + """Fill a TypedDict-backed form, submit it, and verify the data arrives. + + Args: + driver: selenium WebDriver open to the app + typeddict_form: harness for the app + """ + assert typeddict_form.app_instance is not None, "app is not running" + + token_input = AppHarness.poll_for_or_raise_timeout( + lambda: driver.find_element(By.ID, "token") + ) + token = typeddict_form.poll_for_value(token_input) + assert token + + app_source = typeddict_form.app_source + is_inherited = ( + isinstance(app_source, functools.partial) + and app_source.func is TypedDictInheritedFormSubmit + ) + + if is_inherited: + email_input = driver.find_element(By.NAME, "email") + email_input.send_keys("user@example.com") + + nickname_input = driver.find_element(By.NAME, "nickname") + nickname_input.send_keys("cooluser") + else: + name_input = driver.find_element(By.NAME, "name") + name_input.send_keys("Alice") + + email_input = driver.find_element(By.NAME, "email") + email_input.send_keys("alice@example.com") + + message_input = driver.find_element(By.TAG_NAME, "textarea") + message_input.send_keys("Hello there") + + await asyncio.sleep(0.5) + + prev_url = driver.current_url + + submit_btn = driver.find_element(By.CLASS_NAME, "rt-Button") + submit_btn.click() + + typeddict_form.poll_for_content( + driver.find_element(By.ID, "form-data"), exp_not_equal="{}" + ) + form_data = json.loads(driver.find_element(By.ID, "form-data").text) + assert isinstance(form_data, dict) + form_data = format.collect_form_dict_names(form_data) + + if is_inherited: + assert form_data["email"] == "user@example.com" + assert form_data["nickname"] == "cooluser" + else: + assert form_data["name"] == "Alice" + assert form_data["email"] == "alice@example.com" + assert form_data["message"] == "Hello there" + + # submitting the form should NOT change the url (preventDefault) + assert driver.current_url == prev_url diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index 6f2d61e5a64..fcccddaa7fc 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -160,6 +160,48 @@ def on_submit(self, form_data: SignupData): assert "Matching fields present in the form" in error +def test_on_submit_accepts_typed_dict_with_inherited_optional_fields(): + """Inherited optional TypedDict keys should remain optional.""" + + class BaseSignupData(TypedDict, total=False): + nickname: str + + class SignupData(BaseSignupData): + email: str + + class SignupState(rx.State): + @rx.event + def on_submit(self, form_data: SignupData): + pass + + form = HTMLForm.create( + Input.create(name="email"), + on_submit=SignupState.on_submit, + ) + + assert isinstance(form.event_triggers["on_submit"], EventChain) + + +def test_on_submit_accepts_controls_associated_via_form_attribute(): + """Controls associated via the HTML form attribute should not fail validation.""" + + class SignupData(TypedDict): + email: str + + class SignupState(rx.State): + @rx.event + def on_submit(self, form_data: SignupData): + pass + + form = HTMLForm.create( + id="signup", + on_submit=SignupState.on_submit, + ) + Input.create(name="email", form="signup") + + assert isinstance(form.event_triggers["on_submit"], EventChain) + + def test_on_submit_typed_dict_skips_dynamic_field_identifiers(): """Dynamic field names should skip strict validation instead of raising.""" From ce8b987200cd18969915d7512d166961d29458c5 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Thu, 16 Apr 2026 16:26:32 +0500 Subject: [PATCH 04/12] fix: handle NotRequired TypedDict fields on Python 3.10 On 3.10, typing.TypedDict ignores typing_extensions.NotRequired when populating __required_keys__. Fall back to annotation inspection to subtract NotRequired fields on older Python versions. --- .../el/elements/forms.py | 40 ++++++++++++++++--- pyi_hashes.json | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index 16d30f9c42d..fe87b5a56df 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -2,10 +2,11 @@ from __future__ import annotations +import sys from collections.abc import Iterator from functools import partial from hashlib import md5 -from typing import Any, ClassVar, Literal, get_type_hints +from typing import Any, ClassVar, Literal, get_origin, get_type_hints from reflex_base.components.component import BaseComponent, Component, field from reflex_base.components.tags.tag import Tag @@ -31,7 +32,7 @@ from reflex_base.vars import VarData from reflex_base.vars.base import LiteralVar, Var from reflex_base.vars.number import ternary_operation -from typing_extensions import is_typeddict +from typing_extensions import NotRequired, is_typeddict from reflex_components_core.el.element import Element @@ -134,6 +135,37 @@ def _get_static_string_prop( return None +def _get_required_typed_dict_fields(typed_dict_type: type[Any]) -> frozenset[str]: + """Resolve required TypedDict keys across Python versions. + + On Python 3.11+ ``__required_keys__`` is reliable. On 3.10, + ``typing.TypedDict`` combined with ``typing_extensions.NotRequired`` + fails to populate ``__required_keys__``, so we patch the result by + subtracting fields whose annotation is wrapped with ``NotRequired``. + + Args: + typed_dict_type: The TypedDict class to inspect. + + Returns: + The required field names for the TypedDict. + """ + required = frozenset(getattr(typed_dict_type, "__required_keys__", frozenset())) + if sys.version_info >= (3, 11): + return required + + # On 3.10, __required_keys__ ignores NotRequired from typing_extensions. + # Subtract any field explicitly marked NotRequired. + try: + hints = get_type_hints(typed_dict_type, include_extras=True) + except Exception: + return required + + not_required = frozenset( + name for name, hint in hints.items() if get_origin(hint) is NotRequired + ) + return required - not_required + + def _format_field_list(fields: tuple[str, ...]) -> str: """Format field names as a bullet list. @@ -413,9 +445,7 @@ def _validate_on_submit_typed_dict_fields(self) -> None: if not is_typeddict(annotation): continue - required_fields = frozenset( - getattr(annotation, "__required_keys__", frozenset()) - ) + required_fields = _get_required_typed_dict_fields(annotation) typed_dict_contracts.append(( event.handler.fn.__qualname__, annotation, diff --git a/pyi_hashes.json b/pyi_hashes.json index 33e8f9b0c6c..2546c3a2ada 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -27,7 +27,7 @@ "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "7304756e0a69059394a136bb6b2c70c2", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "e6c845f2f29eb079697a2e31b0c2f23a", "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "9675fd9f996f25b5507fe28f94681d13", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "e0397b0e23a2b0787209309023002998", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "661045d4abc29037b148fc2ad5c68bd1", "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "75a8156bd7fae17ed400f4aa00eff686", "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "c84acdf6241f012faffa042b00095827", "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "91cd0fedd809afa43dbd4ecce9448bf5", From 07cc0e2e9898c9ecdb400e6fbb3b8370369eec49 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Thu, 16 Apr 2026 17:03:34 +0500 Subject: [PATCH 05/12] test: add RED/GREEN assertions and data-driven integration tests Unit tests now pair happy paths with failing counterparts to prove validation is active. Integration test carries input/expected data per variant instead of fragile runtime app_source detection. --- .../integration/test_typeddict_form_submit.py | 100 ++++++++++-------- tests/units/components/forms/test_form.py | 50 ++++++++- 2 files changed, 105 insertions(+), 45 deletions(-) diff --git a/tests/integration/test_typeddict_form_submit.py b/tests/integration/test_typeddict_form_submit.py index 6233e768554..976a716e136 100644 --- a/tests/integration/test_typeddict_form_submit.py +++ b/tests/integration/test_typeddict_form_submit.py @@ -108,12 +108,40 @@ def index(): ) +# Each variant carries its own input actions and expected output. +_CONTACT_FIELDS = { + "inputs": {"name": "Alice", "email": "alice@example.com"}, + "textarea": "Hello there", + "expected": { + "name": "Alice", + "email": "alice@example.com", + "message": "Hello there", + }, +} +_INHERITED_FIELDS = { + "inputs": {"email": "user@example.com", "nickname": "cooluser"}, + "textarea": None, + "expected": {"email": "user@example.com", "nickname": "cooluser"}, +} + + @pytest.fixture( scope="module", params=[ - functools.partial(TypedDictFormSubmit, form_component="rx.form.root"), - functools.partial(TypedDictFormSubmit, form_component="rx.el.form"), - functools.partial(TypedDictInheritedFormSubmit, form_component="rx.el.form"), + ( + functools.partial(TypedDictFormSubmit, form_component="rx.form.root"), + _CONTACT_FIELDS, + ), + ( + functools.partial(TypedDictFormSubmit, form_component="rx.el.form"), + _CONTACT_FIELDS, + ), + ( + functools.partial( + TypedDictInheritedFormSubmit, form_component="rx.el.form" + ), + _INHERITED_FIELDS, + ), ], ids=[ "typeddict-radix", @@ -121,7 +149,9 @@ def index(): "inherited-html", ], ) -def typeddict_form(request, tmp_path_factory) -> Generator[AppHarness, None, None]: +def typeddict_form( + request, tmp_path_factory +) -> Generator[tuple[AppHarness, dict], None, None]: """Start a TypedDict form app at tmp_path via AppHarness. Args: @@ -129,29 +159,31 @@ def typeddict_form(request, tmp_path_factory) -> Generator[AppHarness, None, Non tmp_path_factory: pytest tmp_path_factory fixture Yields: - running AppHarness instance + running AppHarness instance and its test field config """ + app_source, fields = request.param param_id = request._pyfuncitem.callspec.id.replace("-", "_") with AppHarness.create( root=tmp_path_factory.mktemp("typeddict_form"), - app_source=request.param, - app_name=request.param.func.__name__ + f"_{param_id}", + app_source=app_source, + app_name=app_source.func.__name__ + f"_{param_id}", ) as harness: assert harness.app_instance is not None, "app is not running" - yield harness + yield harness, fields @pytest.fixture -def driver(typeddict_form: AppHarness): +def driver(typeddict_form: tuple[AppHarness, dict]): """Get an instance of the browser open to the app. Args: - typeddict_form: harness for the TypedDict form app + typeddict_form: harness and fields for the TypedDict form app Yields: WebDriver instance. """ - driver = typeddict_form.frontend() + harness, _ = typeddict_form + driver = harness.frontend() try: yield driver finally: @@ -159,42 +191,29 @@ def driver(typeddict_form: AppHarness): @pytest.mark.asyncio -async def test_typeddict_form_submit(driver, typeddict_form: AppHarness): +async def test_typeddict_form_submit(driver, typeddict_form: tuple[AppHarness, dict]): """Fill a TypedDict-backed form, submit it, and verify the data arrives. Args: driver: selenium WebDriver open to the app - typeddict_form: harness for the app + typeddict_form: harness and fields for the app """ - assert typeddict_form.app_instance is not None, "app is not running" + harness, fields = typeddict_form + assert harness.app_instance is not None, "app is not running" token_input = AppHarness.poll_for_or_raise_timeout( lambda: driver.find_element(By.ID, "token") ) - token = typeddict_form.poll_for_value(token_input) + token = harness.poll_for_value(token_input) assert token - app_source = typeddict_form.app_source - is_inherited = ( - isinstance(app_source, functools.partial) - and app_source.func is TypedDictInheritedFormSubmit - ) - - if is_inherited: - email_input = driver.find_element(By.NAME, "email") - email_input.send_keys("user@example.com") - - nickname_input = driver.find_element(By.NAME, "nickname") - nickname_input.send_keys("cooluser") - else: - name_input = driver.find_element(By.NAME, "name") - name_input.send_keys("Alice") - - email_input = driver.find_element(By.NAME, "email") - email_input.send_keys("alice@example.com") + for input_name, input_value in fields["inputs"].items(): + el = driver.find_element(By.NAME, input_name) + el.send_keys(input_value) - message_input = driver.find_element(By.TAG_NAME, "textarea") - message_input.send_keys("Hello there") + if fields["textarea"] is not None: + textarea = driver.find_element(By.TAG_NAME, "textarea") + textarea.send_keys(fields["textarea"]) await asyncio.sleep(0.5) @@ -203,20 +222,15 @@ async def test_typeddict_form_submit(driver, typeddict_form: AppHarness): submit_btn = driver.find_element(By.CLASS_NAME, "rt-Button") submit_btn.click() - typeddict_form.poll_for_content( + harness.poll_for_content( driver.find_element(By.ID, "form-data"), exp_not_equal="{}" ) form_data = json.loads(driver.find_element(By.ID, "form-data").text) assert isinstance(form_data, dict) form_data = format.collect_form_dict_names(form_data) - if is_inherited: - assert form_data["email"] == "user@example.com" - assert form_data["nickname"] == "cooluser" - else: - assert form_data["name"] == "Alice" - assert form_data["email"] == "alice@example.com" - assert form_data["message"] == "Hello there" + for key, expected_value in fields["expected"].items(): + assert form_data[key] == expected_value, f"Mismatch for {key!r}" # submitting the form should NOT change the url (preventDefault) assert driver.current_url == prev_url diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index fcccddaa7fc..6bed5e248a9 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -84,13 +84,30 @@ class SignupState(rx.State): def on_submit(self, form_data: SignupData): pass + # RED: without NotRequired handling, nickname would be treated as required + # and the form below (which only has "email") would raise. form = HTMLForm.create( Input.create(name="email"), on_submit=SignupState.on_submit, ) - assert isinstance(form.event_triggers["on_submit"], EventChain) + # Prove validation is active: a truly missing required field still raises. + class StrictData(TypedDict): + email: str + nickname: str + + class StrictState(rx.State): + @rx.event + def on_submit(self, form_data: StrictData): + pass + + with pytest.raises(EventHandlerValueError): + HTMLForm.create( + Input.create(name="email"), + on_submit=StrictState.on_submit, + ) + def test_on_submit_allows_extra_typed_dict_form_fields(): """Forms may include more fields than the TypedDict requires.""" @@ -174,13 +191,22 @@ class SignupState(rx.State): def on_submit(self, form_data: SignupData): pass + # RED: without proper inheritance handling, nickname (from the total=False + # parent) would be treated as required, and this form would raise. form = HTMLForm.create( Input.create(name="email"), on_submit=SignupState.on_submit, ) - assert isinstance(form.event_triggers["on_submit"], EventChain) + # Prove the inherited field IS accepted when provided. + form_with_both = HTMLForm.create( + Input.create(name="email"), + Input.create(name="nickname"), + on_submit=SignupState.on_submit, + ) + assert isinstance(form_with_both.event_triggers["on_submit"], EventChain) + def test_on_submit_accepts_controls_associated_via_form_attribute(): """Controls associated via the HTML form attribute should not fail validation.""" @@ -193,6 +219,10 @@ class SignupState(rx.State): def on_submit(self, form_data: SignupData): pass + # RED: without the form-id escape hatch, this would raise + # EventHandlerValueError because the form has no child inputs + # matching the TypedDict's required "email" field. + # (The input is associated externally via form="signup".) form = HTMLForm.create( id="signup", on_submit=SignupState.on_submit, @@ -201,6 +231,12 @@ def on_submit(self, form_data: SignupData): assert isinstance(form.event_triggers["on_submit"], EventChain) + # Verify it WOULD fail without the id (proving the escape hatch matters). + with pytest.raises(EventHandlerValueError): + HTMLForm.create( + on_submit=SignupState.on_submit, + ) + def test_on_submit_typed_dict_skips_dynamic_field_identifiers(): """Dynamic field names should skip strict validation instead of raising.""" @@ -213,9 +249,19 @@ class SignupState(rx.State): def on_submit(self, form_data: SignupData): pass + # RED: without the dynamic-field escape hatch, this would raise + # because "email" isn't statically present. The dynamic Var name + # could resolve to "email" at runtime, so validation must be skipped. form = HTMLForm.create( Input.create(name=Var(_js_expr="dynamic_name", _var_type=str)), on_submit=SignupState.on_submit, ) assert isinstance(form.event_triggers["on_submit"], EventChain) + + # Verify it WOULD fail with a static non-matching name. + with pytest.raises(EventHandlerValueError): + HTMLForm.create( + Input.create(name="wrong_field"), + on_submit=SignupState.on_submit, + ) From a4512109345d909f9c8d244b578990a1f49a8f94 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Thu, 16 Apr 2026 18:58:16 +0500 Subject: [PATCH 06/12] test: fix flaky test --- pyi_hashes.json | 240 ++++++++++++++++------------- tests/integration/test_tailwind.py | 2 +- 2 files changed, 131 insertions(+), 111 deletions(-) diff --git a/pyi_hashes.json b/pyi_hashes.json index 2546c3a2ada..7c6ce659aa4 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,124 +1,144 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "03a6445206663c5c3de5c77b943ae42d", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "6d0d409dca87762db4f00adda6c98669", + "packages/reflex-base/src/reflex_base/constants/base.pyi": "03dfb9ac1524d47dbbddaa0d298c5173", + "packages/reflex-base/src/reflex_base/constants/compiler.pyi": "ffce11c834d05320e58c00b8535d244e", + "packages/reflex-base/src/reflex_base/constants/config.pyi": "6005f00dc68f2bfc867497ab0e15ab59", + "packages/reflex-base/src/reflex_base/constants/custom_components.pyi": "cef0d07633a9bf1c89fb07c0ae4f46b6", + "packages/reflex-base/src/reflex_base/constants/event.pyi": "8c116228f8b29e2b8c6574995b5f6576", + "packages/reflex-base/src/reflex_base/constants/installer.pyi": "7bc747369c17bed33087d42124ca7e41", + "packages/reflex-base/src/reflex_base/constants/route.pyi": "921b1678ab44786354e5096f136ebefa", + "packages/reflex-base/src/reflex_base/plugins/sitemap.pyi": "c75f99f8093acd4bb879001e276031e3", + "packages/reflex-base/src/reflex_base/plugins/tailwind_v3.pyi": "2195ceb3b60ff524021a4a3f4df98602", + "packages/reflex-base/src/reflex_base/plugins/tailwind_v4.pyi": "8ce6ecda30b592759ab2d9a53afe16d4", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "5249d934c64f99ecefe0f5907bfb1eb1", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "60d916b71e20b2c37ee85c2f77cb1a94", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "82b29d23f2490161d42fd21021bd39c3", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "7009187aaaf191814d031e5462c48318", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "a361a12156aa65e053cc798678ccdfa8", - "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "bcc9c4f0b3088b34b7baa52356365271", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "8a8620e6837d7f9b6182711fd8d3a1d4", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "f5f0fce5f571af88e6aeb9e2acb9b74b", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "d63ceaa2beb41fed8f180c7c20cb2f9b", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "0c5eacde18185e690e6756bab2e13c59", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "7e56f60da63a68f6cc430c3ad34c91b9", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "466925a49ad56eca763fae214be03caa", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "60016e1eae752770bddb39361ff015fa", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "7e6d47b5103645de33309dc4ac1a4317", + "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "401472c41e11c629598e5b6200434a18", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "1852d2a5b49961a6e164b65bcf153b4a", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "49fb5ade8b957091a5a5a7c98e6feafb", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "ed61f55dd75ae4df5cc14ee01a6540fa", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "ed0bf9115a1b4f1f51975c852190dbb2", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "762e78d2b1e1c5632afcc0652ca467af", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "cdc59d7bdde7b9b3b10845aa52853299", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "bb692191ce0f1fbfce859a9eba10cbad", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "dd5142b3c9087bf2bf22651adf6f2724", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "dd8a55481eaa165e396d08a737a841eb", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "62acf1e2b977f76779742743b4e1debd", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "80624ed0df3475134478bc2e5094545e", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "8f089d215454cc8af2b52bb154dc3219", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "9c2cfc88c5081d7cdf4963fd3c79509c", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "eb84eb0bacdc3bb5381f724ff7098a7b", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "0a4776b3b0da5517962bb5cf3124c6c5", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "c0e70589f0038928eabd15e8f4ae91d4", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "98ce2d7f4747ac41062d7355c12a7c1a", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "d63f077b0c4cd1924c59a6562d558e39", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "2f81188abc7a1c8fc2e7573e4335ba63", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "289f617c1646449bddef17f8f124999e", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "b2e1538d01308e52070caff4cbef4ebe", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "5d542049d242432da93bdb37f064232c", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "90f372ef93b742ed445a6afa8b671e5c", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "fe237bf5b5c0030cb85c7b72b2eabdfe", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "53b1cc93462e5048990aa8eb1d8404ca", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "9b3d5c53ccaa3382d72c00cdc3743b69", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "c96fed4da42a13576d64f84e3c7cb25c", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "f09129ddefb57ab4c7769c86dc9a3153", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "7304756e0a69059394a136bb6b2c70c2", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "19bd843c1294785ec42b3e0f6ae5e46f", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "e6c845f2f29eb079697a2e31b0c2f23a", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "9675fd9f996f25b5507fe28f94681d13", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "661045d4abc29037b148fc2ad5c68bd1", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "75a8156bd7fae17ed400f4aa00eff686", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "c84acdf6241f012faffa042b00095827", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "91cd0fedd809afa43dbd4ecce9448bf5", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "211128cc979178dbc67ef3763d0a86db", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "e7b3005bce566a5a940f576ad398c816", - "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "f7b21830f2583f77e6fb114e9175245a", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "08ff480b1865071d2984fe346919e2b6", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "d2df8254fc205746f377f01c618b5def", - "packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "411ed5fa8269441976d78759598de5da", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "90879e8fd9edc76c0c933c7be303de89", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "b1825d05d19baaca2126f01567a98aab", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "c545c02a893b8ad65784f366a0bdce33", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "db36cec6ad9b696f186e7a85b6d4eceb", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "3b8b6ce3878a38ef1c745e59ac5f128b", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "e6d95d7f73925d34887f48c7a5fdc996", - "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "b2f485bfde4978047b7b944cf15d92cb", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "71d2b2c818560a31160456ae9051f79d", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "5184631a830edd5c7adbf5c7767ee400", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "137264c6ba293c50b4cf8c1fc7dbbaa3", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "4c032857b1b6a62cd4865432b841917d", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "8ab85b96e72433ac67df319799830530", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "9824faa7115bba66a3b0e827022c8cf1", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "74f90ea113cecedb1f1bac911c642049", + "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "55eeee66bb8a07af0e73885d61ce819c", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "5b405c52b5f0b703ab656a94bc3e02d3", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "a04f2577f0358109a428780aac2e78ae", + "packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "b5f6d8ce3fcdfc1d5efd5dfac2b95e3b", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "69b644aa2d116ea3532155ada09be70a", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "74ce1f8a302aa9b9b317e6e63e5dac41", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "7881310ab9c39c6627bb64ab35513590", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "a30df34c9b12979bc9a87562fe125772", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "2623f3930c838f52a13018fb5ded01c0", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "700dac836a2610094c4e85e068d2c8f7", + "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "19216eb3618f68c8a76e5e43801cf4af", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "5404a8da97e8b5129133d7f300e3f642", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "ab1fcc6978810c0c3f93ab41d4b3d086", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "900fc82e6b90e3bd10fde20f94314c5e", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "0f6b97d2c5a779e1f0df103541d12092", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "b8f3b64dfb6b0bf49d5fc5d5efe565ab", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "50818b6f977013a55928df9066f8b160", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "c1ae974455eceaaa8476742e4a7fdff3", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "4abbecc560204625ba8d63e3d39541ad", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e48ed5a3fbe79eb73e63ad07aa490241", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "44ae9d7d31f4c0237d9b8ce213454218", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "170dc8766164a4021770ac0447e7978e", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "5bd53bb47323cc26a4e0c4b64f93cbaf", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "041125ac50c493621e58758412ea5c02", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "47e86a27f49fbc9c5f2f8f4832f27d30", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "381491180881ae1890396ae1276748f9", "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "b433b9a099dc5b0ab008d02c85d38059", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "299a1facb8ac556fcf704679d65c80b8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "fb9859c6cc85d8d479f84b0fa5902f20", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "6210c4383081524f9e521441247fdc3a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "56ed24c55adcfcc1665e3c2b1f5bca4f", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "f10f0169f81c78290333da831915762f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "c35a5debc70e2e5029067a62e198eec6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "bc07be00fa24640bd46cf4697fb65ad5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "4f039c85361a40ff8432308333d174bd", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "05ede1daa54ef23711e27fdc962f2122", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "b3a074189f7297d8a1d6fba0400ccfcb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "1723869849f0f92c4d742f476b478666", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "09e3ba7d6e6a7b524c781a27435c0653", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "5f454887169d2c7a3c35274fb3a8fb8d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "8ad1259984e483a79b1dab34004c02fe", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "0e89c6352624f58722813e0d40f52fed", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "88ff2e3147c62fe01cd314222a4df7a8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "9a9992cdaae601669bcde6813dfbaaaa", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "b6f7c3554c9409aa20a60be52af50795", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "285c18153dc9027e27a318a85e6b66a6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "5f1a07370536b8495a0c0657cd9b07e8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "6a6ef283962fdff0a59df0e715069fec", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "99732b0338386e36bc007d4e972da76d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "01a420df97331567deea35a8fec0e2fa", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "7a80c426081c6c26f293d5c48872ff12", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "3249fcc42afa94504ba91e170d9bd0df", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "3132d260e7265b851cfe722cb24d5380", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "106075864fa2dd6d20ee06b6d8c0065d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "8e818b7a1702e2e347ced15921dcafb1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "d73c673123481148d1fbf2fd17a508df", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "145d2e28aacb581ac163920729895366", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "228b915aa902337e06f83a9fefdf095a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "8d4c7928cb9e766616131af7aa055557", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "da395105458d21add8731d71ecadfb81", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "76177543a378cbc659e78ac79e9f7eeb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "cb0c2754036c7dd62ff686ae327f37c8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "f63f2ee2d7d0c43249ce9caf14d067bf", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "b1b26fb80f6efd50f68e8a4c22542b00", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "4f6c08f36ef1b07bb61ba6ef6e63bfd3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "62ed931cdc3cbecda82973730c4d5ac8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "7e78f23538ad93e7101372b973ebcf37", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "dcbb1dc8e860379188924c15dd21605b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "c0237feba5c808f10f0ac2a65f2c93c6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "f2ad9209f2ea21346206bec7e5fcf323", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "34a72530b228241b128f852d66a733ab", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "d96c741e5a7585b47cd221f4f797442f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "c772ba7b2b631ffc51c8262504b03303", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "e537f6dd3565e29ea9481baef34f2be2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "6ebda83ce04060969d0e819d8f9ea43e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "6984bbeab128335148eef9a5d017366e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "87f47ab58c33cf18d45dc13dea93d36a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "2afa52f3d8bb6a171f92b67a3c1cbad2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "cf4ee8d5881ae637d3921ce47d77288c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "fc59489d19ced57d25b79ed3faea1452", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "44c8f858b0dd5a0b2cf3a123819800ca", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "4aaf303dda33666f843497f739372f31", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "9513c578fd0937af79e844b862247e07", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "6dc3cc08b55e609ec9fc8e1fe50dc88d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "7fc1a0118435b5326e87218486ba0467", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "c1a14bc5ef590547fceb75b0b7ba8133", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "386b07f457781c167e70d57cfee33264", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "d888952bbd5e8df885e7ba7b9c7c6894", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "d82544e85d3c47ebddb6b1fee6216f78", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "48e0d7e25cc418e10e3d6f270a1cbcb2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "325f54a610ddca5679ec555220207caa", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "794d02c236c887de7572d7b1fa730d64", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "30867282512dd968e6570e5aad7b52aa", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "19d3d20ea46171346a1c3e488ebd1a3a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "75991b362962882f18c69e081e68c284", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "430834b6a91bca074447f9c4b046d7d2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "81e92f87d352f75e1e09f8f44cc782f5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "f452755354bec757ebf01817de731bda", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "4e296a069c395154825e51abc8b37abb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "ba6f983a6dbee4fd9665bb63544152d4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "3b746f28fb7f9518697edb2dfc0a2c90", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "b10ab137cf809461cec840b8ded3eb4d", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "e21ac6b7fb0501ad68de76a32f7ecf53", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "0885e5b47e0f158511b26c111ccea9ba", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "b4abd9619577a34783f7b41e4041170c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "e714426507967f2d0d1feebb8ca8c005", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "07e7d6a75c5bb5673d2fc6992ac05b6a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "5d97af6f1bacf7b29ff709922f6835d5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "5ca834b3dae21884223fd76f485c7c04", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "445695ca79d8efebadf60201c17c9d98", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "609fff762d18b6326b87a4f1dfbddc25", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "e7215f6ebde268e6ff27c7f3f65b91e2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "e6f6007a6e5ba3ace5f8d761992107c7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "9e452af27229b676ad0146e40f75bed5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "0a60d38f462928eccd5f2e1522f8d9e5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "99a43eb5e4f64a9670587f7c4c33e1ab", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "f7995de1ca82dca1fb0a52baa6221e06", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "ac557baa161dd9f4fd5ba4239dc6f3e7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "045892084d19790ec5dce0aca23f8ab6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "e09b30f3045fffe6415df56c2ef6b801", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "081c2dc6b6bdd9ac58865b605f51d73a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "7ee955218a908d3ff4724d6f870536f9", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "1f3a21cbcc69131135c069d200ddd432", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "71540d9403ebbdac97fa21096b5c01e9", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "de7ee994f66a4c1d1a6ac2ad3370c30e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "1b071289b2f47735add7519331b53301", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "d3ad0524bebadd4a4aa88fa81b72e9a6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "61f99428ce61bd4969cf6066743d4521", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "8614a69c3697368b04eef70706d35fd0", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "deefd1590bdc2d42a6878972e4bbbe78", - "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "b6ea61f730b2ad16d9d4eaf5dab1f57c", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "d62abd377ce01f9369f84f4c387ae2d3", - "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "cd324d4dcd9c98774aca788e5cb8624b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "ef67ff3ea9805bd95322834f044437cb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "636acf47e8aac0cda0ab2b5beb4119de", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "bc56e9eb91ae9899560c613d55089375", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "f4dad92290f4bb8cb65e7702d41d23de", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "179be574a5127a877780d2839fcae7ee", + "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "5bbf08502695fededb3ca4d23245d3df", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "41e57798ed7df7eb405d0ac532289c1b", + "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "ae525af69173fab76a89924d10e07209", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "7b8b69840a3637c1f1cac45ba815cccf", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "5f4aa756dcb27f4c9d439f5067c33567", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "46f30814a6992ea8fef6b620892d0d38", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "099962a5883d861e8c294d97d727f85b", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "6d998f7745cf21548f873995536aa1d3", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "70c952c1f0f403d6b9fa5d8c04eb6802", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "2a83640d04139b66aa6ed493daaec336", - "reflex/__init__.pyi": "5de3d4af8ea86e9755f622510b868196", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "1d4ecc60531f713c9ff5948370ec5657", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "45e7fbe250684e338a6ac5c74a6c6b74", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "f3b067ecc8e66cb2698d21acb0016a32", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ad9783b38fda94d9c64ac808a7f2535", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "16735f4e05635c022d014eecb8fd2b5e", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "13e2466062abe801d0741b55a39ae978", + "packages/reflex-ui-shared/src/reflex_ui_shared/components/image_zoom.pyi": "427e5cbba88b6c780408d6b1cb8392d9", + "packages/reflex-ui-shared/src/reflex_ui_shared/components/marketing_button.pyi": "79e7f2edfe5b9e847c0472f2940fe083", + "packages/reflex-ui-shared/src/reflex_ui_shared/components/marquee.pyi": "6faf41ad3a6a6b6bae93d53b83f6992d", + "packages/reflex-ui/src/reflex_ui/__init__.pyi": "8227e61a3651ec383f2930dc81cc5829", + "packages/reflex-ui/src/reflex_ui/blocks/calcom.pyi": "5f262fe1820f4f6c3d6f6c472effd1ee", + "packages/reflex-ui/src/reflex_ui/blocks/plain.pyi": "9627d6e826406ddb47ace03428a8c1a1", + "packages/reflex-ui/src/reflex_ui/components/__init__.pyi": "e84a76ef3107b12ac456f6b8db958e02", + "packages/reflex-ui/src/reflex_ui/components/icons/__init__.pyi": "3ec0612592d2182e1fc0378a62fac5db", + "packages/reflex-ui/src/reflex_ui/components/icons/simple_icon.pyi": "21d3b300bd880c82eea8eb4c0245379c", + "packages/reflex-ui/src/reflex_ui/utils/__init__.pyi": "4ba69b488af43f304510d8340b78a15e", + "reflex/__init__.pyi": "3a9bb8544cbc338ffaf0a5927d9156df", "reflex/components/__init__.pyi": "f39a2af77f438fa243c58c965f19d42e", - "reflex/experimental/memo.pyi": "76ffe267e664cd2778f08df30f78de7e" + "reflex/experimental/memo.pyi": "15bed78c87e7c7da9c16355953b266b7" } diff --git a/tests/integration/test_tailwind.py b/tests/integration/test_tailwind.py index 071570ce44a..e501126edfe 100644 --- a/tests/integration/test_tailwind.py +++ b/tests/integration/test_tailwind.py @@ -42,7 +42,7 @@ def index(): id="p-content", ) - assets = Path(__file__).resolve().parent.parent / "assets" + assets = Path("assets") assets.mkdir(exist_ok=True) stylesheet = assets / "test_styles.css" stylesheet.write_text(".external { color: rgba(0, 0, 255, 0.5) }") From 80be5f4e0ecfd174b2f28ed36175eb6aa9d5face Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Thu, 16 Apr 2026 19:33:23 +0500 Subject: [PATCH 07/12] pyi hashes --- pyi_hashes.json | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/pyi_hashes.json b/pyi_hashes.json index 7c6ce659aa4..a97cfd83ae9 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,14 +1,4 @@ { - "packages/reflex-base/src/reflex_base/constants/base.pyi": "03dfb9ac1524d47dbbddaa0d298c5173", - "packages/reflex-base/src/reflex_base/constants/compiler.pyi": "ffce11c834d05320e58c00b8535d244e", - "packages/reflex-base/src/reflex_base/constants/config.pyi": "6005f00dc68f2bfc867497ab0e15ab59", - "packages/reflex-base/src/reflex_base/constants/custom_components.pyi": "cef0d07633a9bf1c89fb07c0ae4f46b6", - "packages/reflex-base/src/reflex_base/constants/event.pyi": "8c116228f8b29e2b8c6574995b5f6576", - "packages/reflex-base/src/reflex_base/constants/installer.pyi": "7bc747369c17bed33087d42124ca7e41", - "packages/reflex-base/src/reflex_base/constants/route.pyi": "921b1678ab44786354e5096f136ebefa", - "packages/reflex-base/src/reflex_base/plugins/sitemap.pyi": "c75f99f8093acd4bb879001e276031e3", - "packages/reflex-base/src/reflex_base/plugins/tailwind_v3.pyi": "2195ceb3b60ff524021a4a3f4df98602", - "packages/reflex-base/src/reflex_base/plugins/tailwind_v4.pyi": "8ce6ecda30b592759ab2d9a53afe16d4", "packages/reflex-components-code/src/reflex_components_code/code.pyi": "5249d934c64f99ecefe0f5907bfb1eb1", "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "60d916b71e20b2c37ee85c2f77cb1a94", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "82b29d23f2490161d42fd21021bd39c3", @@ -128,16 +118,6 @@ "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ad9783b38fda94d9c64ac808a7f2535", "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "16735f4e05635c022d014eecb8fd2b5e", "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "13e2466062abe801d0741b55a39ae978", - "packages/reflex-ui-shared/src/reflex_ui_shared/components/image_zoom.pyi": "427e5cbba88b6c780408d6b1cb8392d9", - "packages/reflex-ui-shared/src/reflex_ui_shared/components/marketing_button.pyi": "79e7f2edfe5b9e847c0472f2940fe083", - "packages/reflex-ui-shared/src/reflex_ui_shared/components/marquee.pyi": "6faf41ad3a6a6b6bae93d53b83f6992d", - "packages/reflex-ui/src/reflex_ui/__init__.pyi": "8227e61a3651ec383f2930dc81cc5829", - "packages/reflex-ui/src/reflex_ui/blocks/calcom.pyi": "5f262fe1820f4f6c3d6f6c472effd1ee", - "packages/reflex-ui/src/reflex_ui/blocks/plain.pyi": "9627d6e826406ddb47ace03428a8c1a1", - "packages/reflex-ui/src/reflex_ui/components/__init__.pyi": "e84a76ef3107b12ac456f6b8db958e02", - "packages/reflex-ui/src/reflex_ui/components/icons/__init__.pyi": "3ec0612592d2182e1fc0378a62fac5db", - "packages/reflex-ui/src/reflex_ui/components/icons/simple_icon.pyi": "21d3b300bd880c82eea8eb4c0245379c", - "packages/reflex-ui/src/reflex_ui/utils/__init__.pyi": "4ba69b488af43f304510d8340b78a15e", "reflex/__init__.pyi": "3a9bb8544cbc338ffaf0a5927d9156df", "reflex/components/__init__.pyi": "f39a2af77f438fa243c58c965f19d42e", "reflex/experimental/memo.pyi": "15bed78c87e7c7da9c16355953b266b7" From 34863e075f38e7f33295a088f1a905be7e83790c Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Sun, 10 May 2026 13:32:36 -0700 Subject: [PATCH 08/12] update pyi_files --- pyi_hashes.json | 214 ++++++++++++++++++++++++------------------------ 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/pyi_hashes.json b/pyi_hashes.json index 75a193845f0..4c7a2cf8307 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,124 +1,124 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "6ef91a4a4976e66b2761539e16d4f28e", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "d3e0c33fdc34f5c154ac387d550c0d29", + "packages/reflex-components-code/src/reflex_components_code/code.pyi": "ee1377bb8779bcd9a1069246cc41f957", + "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "60d916b71e20b2c37ee85c2f77cb1a94", "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "82b29d23f2490161d42fd21021bd39c3", "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "7009187aaaf191814d031e5462c48318", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "ecccfd8a9b0e8b2f4128ff13ff27a9da", - "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "2535814d409e5feaf57da63dcf0abeaf", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "a2e67a9814dc61853ca2299d9d9c698d", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "59170074a1a228ce58685f3f207954f2", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "e4cbfc46eabb904596be4372392add35", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "005866cf4d1cc8ac7693ed6baeca2289", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "0cfa2d8c52321ce7440e887d03007d5b", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "bfc7fb609b822f597d1141595f8090fe", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "8ee129808abb4389cbd77a1736190eae", + "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "7e6d47b5103645de33309dc4ac1a4317", + "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "401472c41e11c629598e5b6200434a18", + "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "1852d2a5b49961a6e164b65bcf153b4a", + "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "49fb5ade8b957091a5a5a7c98e6feafb", + "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "ed61f55dd75ae4df5cc14ee01a6540fa", + "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "581499d67df1d53b4ff57fd660067d9c", + "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "762e78d2b1e1c5632afcc0652ca467af", + "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "cdc59d7bdde7b9b3b10845aa52853299", + "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "bb692191ce0f1fbfce859a9eba10cbad", "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "dd5142b3c9087bf2bf22651adf6f2724", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "918dfad4d5925addd0f741e754b3b076", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "6040fbada9b96c55637a9c8cc21a5e10", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "e3950e0963a6d04299ff58294687e407", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "58138b5f1d5901839729d839620ea4da", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "7fd81a99bde5b0ff94bb52523597fd5c", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "753d6ae315369530dad450ed643f5be6", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "ba60a7d9cba75b27a1133bd63a9fbd59", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "2dd6ba6e3a4d61fc1d79eb582a7cc548", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "5e1dcb1130bc8af282783fae329ae6a6", + "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "d63f077b0c4cd1924c59a6562d558e39", + "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "2f81188abc7a1c8fc2e7573e4335ba63", + "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "289f617c1646449bddef17f8f124999e", + "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "64cd028071ead4892bf5ff4c8d0af34e", + "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "5d542049d242432da93bdb37f064232c", + "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "90f372ef93b742ed445a6afa8b671e5c", + "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "fe237bf5b5c0030cb85c7b72b2eabdfe", + "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "5937accd238b386ed68adc40c88eb5b4", + "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "c51889b4e63f6b3132ae195da959ea1e", "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "c96fed4da42a13576d64f84e3c7cb25c", "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "f09129ddefb57ab4c7769c86dc9a3153", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "ff68d843c5987d3f0d773a6367eb9c63", + "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "19bd843c1294785ec42b3e0f6ae5e46f", "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "e6c845f2f29eb079697a2e31b0c2f23a", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "a3ef8bcb5fe8e4bfb22a8f6d714611b8", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "ab968cdfc51968d6c0c4e8a884c4f246", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "9c1432e70e6b9349f44df04a244a4303", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "f51120c31a1a8b79da9ecf58f19005b9", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "73d19f3d9e389447ad8bbb68e1b7d1c9", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "c86abf00384b5f15725a0daf2533848d", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "903432e316a781b342f2b8d334952da1", - "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "fbbe0bf222d4196c32c88d05cb077997", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "93a69aab9a6f519e3f293d439a39786b", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "2b434f2231d6f21b12d32995ac185e79", - "packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "1074a512195ae23d479c4a2d553954e1", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "8e379fa038c7c6c0672639eb5902934d", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "d2dc211d707c402eb24678a4cba945f7", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "e3ec310276f9d091fbb0261e523ca9ed", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "da02f81678d920a68101c08fe64483a5", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "d6a02e447dfd3c91bba84bcd02722aed", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "91e956633778c6992f04940c69ff7140", + "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "d89d4cf5fa68f7607cc613ba862dbc33", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "e2052378166ece6100c17f5462a3b070", + "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "106c40b2f1732dc8ab5ac3a55c9ade0a", + "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "3ee07d06e7ef9b72129d4315e32aa017", + "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "9bffa93d5d9753fa1123bdf327ef224b", + "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "9824faa7115bba66a3b0e827022c8cf1", + "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "5cf24239b88c1d2847c92a025d05f31d", + "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "55eeee66bb8a07af0e73885d61ce819c", + "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "5c544e26f79477713d9426dfa6003220", + "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "f746255251faf92aa54d5cf28e202758", + "packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "b5f6d8ce3fcdfc1d5efd5dfac2b95e3b", + "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "69b644aa2d116ea3532155ada09be70a", + "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "74ce1f8a302aa9b9b317e6e63e5dac41", + "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "d50c68fcfaebd46e0fa27aa5f0df368b", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "a30df34c9b12979bc9a87562fe125772", + "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "2623f3930c838f52a13018fb5ded01c0", + "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "700dac836a2610094c4e85e068d2c8f7", "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "19216eb3618f68c8a76e5e43801cf4af", "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "5404a8da97e8b5129133d7f300e3f642", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e8ef2b44f2afe3e9b8d678d523673882", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "e779c6739baee98c8a588768a88de45a", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "ffb06f3aa8722c2345a952869118e224", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "cc724f697e62efba294e19b58c6f1bd8", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "4d6121ccc963c64e33c49acd4295eb7a", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "b3b66ec57525c53ea741897e2bc8370e", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "c86bc8d4604e3d8c8d40baad2ac6dc17", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "e48ed5a3fbe79eb73e63ad07aa490241", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "44ae9d7d31f4c0237d9b8ce213454218", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "170dc8766164a4021770ac0447e7978e", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "5bd53bb47323cc26a4e0c4b64f93cbaf", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "041125ac50c493621e58758412ea5c02", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "47e86a27f49fbc9c5f2f8f4832f27d30", + "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "381491180881ae1890396ae1276748f9", "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "b433b9a099dc5b0ab008d02c85d38059", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "e75cbf2a34620721432b1556f3c875cd", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "ed020269e4728cc6abe72354193146b7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "6210c4383081524f9e521441247fdc3a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "56ed24c55adcfcc1665e3c2b1f5bca4f", "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "f10f0169f81c78290333da831915762f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "d5e0419729df4ddf2caf214f40ae7845", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "613abb9870259547c99eb434a3a17512", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "1671e796449b236386d8f53d33e42b2f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "9fde9929ca5197e0e1880bce9a08e926", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "e5d6387a93c74dafaa0d6f1719e08bac", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "31da62c4d8c1d459089aab32cd232feb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "76afb58340c6be1f26b7b110473efa55", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "6cbf013e21d7280118dfd7383998b3bf", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "63b4134246f68f9f556896d6ce194462", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "ec3f89e7d187303344d4127a83522b22", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "99541ee46f112eb4096f903a99f5ffb8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "1dfd91741ff402b3ed93b6daca4939f3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "ed9198da4a7950a8579e50ad970c34ef", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "15f9cee0584414f2d2e0fb82c167f216", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "3f328bb0ba5225e4478febf8c7623833", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "be8eed28e19221a406e554829809ff0d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "9b2adf18f7d239b8e7431f39042ed301", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "4d5813a47b8f8b6ac317ca01d87d9afb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "1ab01f45a4c5ef4211eacc00cc99e4a5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "38a7412205a98617f98218a5b213ada1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "d84b16ac16083a534199fd23659aaa06", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "51fda6313f1ce86d5b1ffdfd68ae8b74", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "bba40e5eae75314157378c9e8b0eea73", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "44770b1f5eb91502bfef3aadd209d0b8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "605479e11d19dd7730c90125b198c9b6", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "519781d33b99c675a12014d400e54d08", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "f4848f7d89abb4c78f6db52c624cdabf", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "61a08374fa19a0bb3f52b8654effc0f2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "530c51742031389d4b2ae43548ff0f03", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "23b21bc11a0012e13ce9bb79b47ba146", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "8364f40600870bafa585528d9cadedf8", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "3a52910c327f55656eb59309f9362361", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "fcf562b2f61ecdcc2de6f70d2ebf9907", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "250b7e77b67e7d8cd3fff2b40526c04c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "799acce0af81899a3a310bdcd43c403b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "cf4ee8d5881ae637d3921ce47d77288c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "fc59489d19ced57d25b79ed3faea1452", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "44c8f858b0dd5a0b2cf3a123819800ca", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "4aaf303dda33666f843497f739372f31", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "9513c578fd0937af79e844b862247e07", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "30f9b953cd5f67a3bee5b862758630c3", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "7fc1a0118435b5326e87218486ba0467", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "c1a14bc5ef590547fceb75b0b7ba8133", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "386b07f457781c167e70d57cfee33264", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "d888952bbd5e8df885e7ba7b9c7c6894", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "d82544e85d3c47ebddb6b1fee6216f78", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "48e0d7e25cc418e10e3d6f270a1cbcb2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "325f54a610ddca5679ec555220207caa", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "794d02c236c887de7572d7b1fa730d64", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "30867282512dd968e6570e5aad7b52aa", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "19d3d20ea46171346a1c3e488ebd1a3a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "75991b362962882f18c69e081e68c284", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "430834b6a91bca074447f9c4b046d7d2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "81e92f87d352f75e1e09f8f44cc782f5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "f452755354bec757ebf01817de731bda", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "4e296a069c395154825e51abc8b37abb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "ba6f983a6dbee4fd9665bb63544152d4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "3b746f28fb7f9518697edb2dfc0a2c90", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "0b5e8273d1d3044772c710f32475f2b4", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "e21ac6b7fb0501ad68de76a32f7ecf53", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "0885e5b47e0f158511b26c111ccea9ba", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "b4abd9619577a34783f7b41e4041170c", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "e714426507967f2d0d1feebb8ca8c005", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "07e7d6a75c5bb5673d2fc6992ac05b6a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "5d97af6f1bacf7b29ff709922f6835d5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "5ca834b3dae21884223fd76f485c7c04", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "445695ca79d8efebadf60201c17c9d98", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "609fff762d18b6326b87a4f1dfbddc25", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "e7215f6ebde268e6ff27c7f3f65b91e2", + "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "e6f6007a6e5ba3ace5f8d761992107c7", "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "9e452af27229b676ad0146e40f75bed5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "5b262189e235cac17182e79188b1681a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "e06c8fd64132765d61b9edb87a48558b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "5aa934d7c6ba3889fa943eabee7dc05f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "c67fafd1aec105cb5a9927ff0e6d2071", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "aa68061a8e5dfd4adf336d1d1cb000fb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "06b92d31331c6f08b5083fcc811b754a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "e7cd3a9cea1c34e21f731f1bd05c1ceb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "8c968fead3155b2d51c687459811b5df", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "cfc8a927642e5b68feabc80080aeb8dc", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "cf88cf870eefaacaf765ead10fb4593b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "0a60d38f462928eccd5f2e1522f8d9e5", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "99a43eb5e4f64a9670587f7c4c33e1ab", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "f7995de1ca82dca1fb0a52baa6221e06", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "ac557baa161dd9f4fd5ba4239dc6f3e7", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "045892084d19790ec5dce0aca23f8ab6", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "e09b30f3045fffe6415df56c2ef6b801", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "081c2dc6b6bdd9ac58865b605f51d73a", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "7ee955218a908d3ff4724d6f870536f9", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "1f3a21cbcc69131135c069d200ddd432", + "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "71540d9403ebbdac97fa21096b5c01e9", "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "de7ee994f66a4c1d1a6ac2ad3370c30e", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "92d5a2df77a69a28a4d591000ee46bd1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "8a1e4376cadf4961212d39a5128a0e4f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "34c7ed3fe1e5f702a98d72751b0052fa", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "619a9d8351748fffe76136002931e583", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "4919daa4483b7c12f6fafd02a2275e0f", - "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "0817c9232a6e4790cff8ea8aa6001950", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "6c1c26149d57c708fab04b82de0eb515", - "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "75207a9fe4f37ec2a2f1becbbbd5237b", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "ef67ff3ea9805bd95322834f044437cb", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "636acf47e8aac0cda0ab2b5beb4119de", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "bc56e9eb91ae9899560c613d55089375", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "f4dad92290f4bb8cb65e7702d41d23de", + "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "179be574a5127a877780d2839fcae7ee", + "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "5bbf08502695fededb3ca4d23245d3df", + "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "41e57798ed7df7eb405d0ac532289c1b", + "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "ae525af69173fab76a89924d10e07209", "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "7b8b69840a3637c1f1cac45ba815cccf", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "277bbf09d72e0c450241f0b7d39ebb60", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "be20d1d71c3b16f7e973a0329c3d81d6", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "5a1a479924ad6184abafe4d796cb04c5", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1979bb6c22bb7a0d3342b2d63fb19d74", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "c5288f311fe37b23539518ba2a3d4482", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "2c5fadcc014056f041cd4d916137d9e7", + "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "1d4ecc60531f713c9ff5948370ec5657", + "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "45e7fbe250684e338a6ac5c74a6c6b74", + "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "64f5e189d4bda0e7d946c302297070c9", + "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "1ad9783b38fda94d9c64ac808a7f2535", + "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "193ab7e39b83b8898feeeed98f21d542", + "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "13e2466062abe801d0741b55a39ae978", "reflex/__init__.pyi": "3a9bb8544cbc338ffaf0a5927d9156df", "reflex/components/__init__.pyi": "f39a2af77f438fa243c58c965f19d42e", - "reflex/experimental/memo.pyi": "82d8699470071df80886a4a6ba8dccfe" + "reflex/experimental/memo.pyi": "57c239cb7e649346fb99a02410c1c941" } From c46f1cabd6d730f5cce99ee628afc5853234ea88 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 11 May 2026 11:19:57 -0700 Subject: [PATCH 09/12] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../src/reflex_components_core/el/elements/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py index f15ec18e0a8..1fbd640f598 100644 --- a/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py +++ b/packages/reflex-components-core/src/reflex_components_core/el/elements/forms.py @@ -447,7 +447,7 @@ def _validate_on_submit_typed_dict_fields(self) -> None: required_fields = _get_required_typed_dict_fields(annotation) typed_dict_contracts.append(( - event.handler.fn.__qualname__, + func.__qualname__, annotation, required_fields, )) From b66c039e2e0120eeb25340720ad13fdd10ac58b1 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 4 Jun 2026 15:09:50 -0700 Subject: [PATCH 10/12] add news fragments --- news/6301.feature.md | 1 + packages/reflex-base/news/6301.feature.md | 1 + packages/reflex-components-core/news/6301.feature.md | 1 + packages/reflex-components-radix/news/6301.misc.md | 1 + 4 files changed, 4 insertions(+) create mode 100644 news/6301.feature.md create mode 100644 packages/reflex-base/news/6301.feature.md create mode 100644 packages/reflex-components-core/news/6301.feature.md create mode 100644 packages/reflex-components-radix/news/6301.misc.md diff --git a/news/6301.feature.md b/news/6301.feature.md new file mode 100644 index 00000000000..4ef22edcf29 --- /dev/null +++ b/news/6301.feature.md @@ -0,0 +1 @@ +`rx.form` `on_submit` handlers can now annotate their form-data parameter with a `TypedDict` (including `typing_extensions.NotRequired` fields). The submitted mapping is accepted by the event-argument type checker, and at component build time the form statically validates that its controls supply every required `TypedDict` field, raising `EventHandlerValueError` — with the missing and present field names — when a required field has no control with a matching static `name`/`id`. Validation is skipped when the form sets an `id` (controls may be associated externally via the HTML `form` attribute) or when any control identifier is a dynamic `Var`. diff --git a/packages/reflex-base/news/6301.feature.md b/packages/reflex-base/news/6301.feature.md new file mode 100644 index 00000000000..4d026f3cba6 --- /dev/null +++ b/packages/reflex-base/news/6301.feature.md @@ -0,0 +1 @@ +Event-argument type checking now treats a mapping-style payload as compatible with a `TypedDict`-annotated callback parameter, scoped narrowly to `on_submit` triggers whose payload is a `Mapping[str, ...]` so unrelated mapping events are unaffected. Adds the `FORM_SUBMIT_MAPPING` type var (exposed on the event namespace and `pyi_generator`'s default imports) and a `Component._is_form_control` class marker that a component sets to declare it contributes a named field to form submission data. diff --git a/packages/reflex-components-core/news/6301.feature.md b/packages/reflex-components-core/news/6301.feature.md new file mode 100644 index 00000000000..daf1d172ea8 --- /dev/null +++ b/packages/reflex-components-core/news/6301.feature.md @@ -0,0 +1 @@ +`Form` now validates statically-knowable fields against a `TypedDict`-annotated `on_submit` handler at create time: it walks nested form controls (including components nested in props), collects their static `name`/`id` values, and raises `EventHandlerValueError` listing the missing and present fields when a required `TypedDict` field has no matching control. `input`, `select`, and `textarea` are marked as form controls so their identifiers are collected, and required-field resolution honors `NotRequired` across Python 3.10 and 3.11+. The `on_submit` handler signature also accepts a mapping-style payload via `on_submit_mapping_event`. diff --git a/packages/reflex-components-radix/news/6301.misc.md b/packages/reflex-components-radix/news/6301.misc.md new file mode 100644 index 00000000000..df0f9faf3de --- /dev/null +++ b/packages/reflex-components-radix/news/6301.misc.md @@ -0,0 +1 @@ +Mark the Radix form controls — checkbox, checkbox group, radio group, radio cards, select, switch, and both sliders — with `_is_form_control` so their static `name`/`id` is collected when a form validates its fields against a `TypedDict`-annotated `on_submit` handler. From fc2686cfddc9a8d83c1c92b45f5432647f5705fd Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 4 Jun 2026 15:18:37 -0700 Subject: [PATCH 11/12] add docs for TypedDict in on_submit handler --- docs/library/forms/form.md | 108 +++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/docs/library/forms/form.md b/docs/library/forms/form.md index a44c47de6b1..d2d7331666b 100644 --- a/docs/library/forms/form.md +++ b/docs/library/forms/form.md @@ -170,6 +170,114 @@ If you need these controls to be passed in the form data even when their values # Video: Forms ``` +## Validating Form Data with a TypedDict + +The `on_submit` handler usually receives the form data as a plain `dict`, which +means accessing a field is untyped (`form_data["email"]` returns `Any`) and a +typo in a `name` goes unnoticed until runtime. + +Instead, you can annotate the handler's parameter with a +[`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict). +This gives you typed, autocompleted access to each field inside the handler, and +Reflex validates the form **at compile time**: every required key of the +`TypedDict` must have a matching form control. If a required field has no +control with that `name` (or `id`), Reflex raises an `EventHandlerValueError` +before the app starts, pointing out exactly which fields are missing. + +```python demo exec +from typing import TypedDict + +from typing_extensions import NotRequired + + +class ContactForm(TypedDict): + first_name: str + last_name: str + email: str + message: NotRequired[str] # optional field + + +class TypedFormState(rx.State): + form_data: ContactForm | None = None + + @rx.event + def handle_submit(self, form_data: ContactForm): + """Handle the form submit.""" + # form_data is typed: editors autocomplete the keys below. + self.form_data = form_data + + +def typed_form_example(): + return rx.vstack( + rx.form( + rx.vstack( + rx.input(placeholder="First Name", name="first_name"), + rx.input(placeholder="Last Name", name="last_name"), + rx.input(placeholder="Email", name="email", type="email"), + rx.text_area(placeholder="Message", name="message"), + rx.button("Submit", type="submit"), + ), + on_submit=TypedFormState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.heading("Results"), + rx.text(TypedFormState.form_data.to_string()), + ) +``` + +### Required and optional fields + +By default every key declared in a `TypedDict` is **required** and must be +backed by a form control. Mark a field as optional with `NotRequired` (or by +inheriting from a `total=False` base) so Reflex won't require a matching +control: + +```python +from typing import TypedDict + +from typing_extensions import NotRequired + + +class ContactForm(TypedDict): + name: str # required: a control named "name" must exist + email: str # required: a control named "email" must exist + message: NotRequired[str] # optional: no control required +``` + +If a required field is missing, creating the form fails fast with a message that +lists the expected, missing, and matching fields: + +```python +class SignupForm(TypedDict): + username: str + email: str + + +class SignupState(rx.State): + @rx.event + def handle_submit(self, form_data: SignupForm): ... + + +# Raises EventHandlerValueError: the form has no control named "email". +rx.form( + rx.input(name="username"), + rx.button("Submit", type="submit"), + on_submit=SignupState.handle_submit, +) +``` + +```md alert info +# When is validation skipped? + +The check only runs when the form fields are statically known. It is +automatically skipped when control `name`/`id` values are dynamic (for example, +built with `rx.foreach`), or when the form has an `id` (since controls can be +associated from elsewhere via the HTML `form` attribute). In those cases the +`TypedDict` still provides typed access inside the handler. At runtime +`form_data` is always a regular dictionary. +``` + ## Dynamic Forms Forms can be dynamically created by iterating through state vars using `rx.foreach`. From 713dd7237b16d08a5e8ecc74739d31a2a0e9fa80 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 4 Jun 2026 15:22:14 -0700 Subject: [PATCH 12/12] bump min dep on reflex-base for reflex-components-{core,radix} these subpackages depend on new structures in reflex-base these dev package mins must be replaced before the final release --- packages/reflex-components-core/pyproject.toml | 2 +- packages/reflex-components-radix/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reflex-components-core/pyproject.toml b/packages/reflex-components-core/pyproject.toml index eab77e789bd..aac2ed49b02 100644 --- a/packages/reflex-components-core/pyproject.toml +++ b/packages/reflex-components-core/pyproject.toml @@ -8,7 +8,7 @@ authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [ - "reflex-base >= 0.9.2", + "reflex-base >= 0.9.4.post23.dev0", "reflex-components-lucide >= 0.9.0", "reflex-components-sonner >= 0.9.0", "python_multipart >= 0.0.21", diff --git a/packages/reflex-components-radix/pyproject.toml b/packages/reflex-components-radix/pyproject.toml index 327eaca0554..bfc2e97c9fd 100644 --- a/packages/reflex-components-radix/pyproject.toml +++ b/packages/reflex-components-radix/pyproject.toml @@ -8,7 +8,7 @@ authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] maintainers = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }] requires-python = ">=3.10" dependencies = [ - "reflex-base >= 0.9.2", + "reflex-base >= 0.9.4.post23.dev0", "reflex-components-core >= 0.9.0", "reflex-components-lucide >= 0.9.0", ]