diff --git a/docs/library/other/memo.md b/docs/library/other/memo.md index 4897955723f..78912a079b2 100644 --- a/docs/library/other/memo.md +++ b/docs/library/other/memo.md @@ -115,6 +115,32 @@ def primary_button( return rx.button(label, rest.merge({"class_name": class_name})) ``` +### Limitation: props consumed at build time + +`rx.RestProp` forwards props to the rendered element at **runtime**, so it can only carry props the element itself understands — real component props and CSS props. It **cannot** carry a prop that the target component's `create()` consumes at **build time** to decide what gets rendered. + +The memo body runs once, when the app compiles — before any caller has passed a value. A value sent later through `rest` arrives only in the browser, after the target's `create()` has already run, so it never reaches that code. It is then emitted as a plain prop or as CSS and silently has no effect. Build-time props include `is_external` on `rx.link` (it selects a router link), the `tag` on `rx.icon` (it picks which icon to import), and any custom `create()` that consumes a keyword to reshape its output. + +To forward such a prop, give it its own `rx.Var[...]` parameter and place it in the body yourself instead of routing it through `rest`: + +```python +class CustomText(rx.el.Span): + @classmethod + def create(cls, *children, prefix: rx.Var[str] | str = "", **props) -> rx.Component: + return super().create(prefix, *children, **props) + + +# `prefix` is consumed by `create`, so it cannot arrive through `rest`. +# Declaring it as a parameter lets the memo body pass it to `create` itself. +@rx.memo +def styled_text(rest: rx.RestProp, *, prefix: rx.Var[str]) -> rx.Component: + return CustomText.create("Foo", rest, prefix=prefix) + + +def index(): + return styled_text(prefix="P: ", class_name="c") +``` + ## Accepting Children diff --git a/packages/reflex-base/news/6605.bugfix.md b/packages/reflex-base/news/6605.bugfix.md new file mode 100644 index 00000000000..fe12ed461fb --- /dev/null +++ b/packages/reflex-base/news/6605.bugfix.md @@ -0,0 +1 @@ +`@rx.memo` functions that forward props through `rx.RestProp` now classify those props the same way a regular component does: a forwarded prop that is not a declared prop of the target (e.g. `font_weight=`) is routed into the component's `css` instead of being passed through as an unrecognized prop and silently dropped. Props the target actually declares are still forwarded normally. diff --git a/packages/reflex-base/src/reflex_base/components/memo.py b/packages/reflex-base/src/reflex_base/components/memo.py index c31e37dd9db..f3b888a8e54 100644 --- a/packages/reflex-base/src/reflex_base/components/memo.py +++ b/packages/reflex-base/src/reflex_base/components/memo.py @@ -39,6 +39,7 @@ ) from reflex_base.constants.state import CAMEL_CASE_MEMO_MARKER from reflex_base.event import EventChain, EventHandler, no_args_event_spec, run_script +from reflex_base.style import convert_dict_to_style_and_format_emotion from reflex_base.utils import console, format from reflex_base.utils.imports import ImportVar from reflex_base.utils.types import safe_issubclass, typehint_issubclass @@ -286,6 +287,11 @@ class MemoComponentDefinition(MemoDefinition): # imports collection, so descendants emit their refs/imports/hooks in the # page scope rather than being duplicated inside the memo body. passthrough_hole_child: Component | None = None + # Field names of the component(s) the body spreads an ``rx.RestProp`` onto, + # populated as a side effect of evaluating the body (``_lift_rest_props``). + # A forwarded prop that is not one of these is a CSS prop, so the call site + # routes it into ``css`` exactly like a non-memo component would. + _rest_target_fields: set[str] = dataclasses.field(default_factory=set) @property def component(self) -> Component: @@ -296,6 +302,18 @@ def component(self) -> Component: """ return self._component.get() + def rest_target_field_names(self) -> set[str]: + """Field names of the body's ``rx.RestProp`` target(s). + + Forces body evaluation so the set is populated before the first call + site needs it. + + Returns: + The union of the rest target components' declared field names. + """ + self._component.get() + return self._rest_target_fields + class MemoComponent(Component): """A rendered instance of a memo component.""" @@ -334,7 +352,11 @@ def _post_init(self, **kwargs): param.bind_call_value(binding) has_rest = _get_rest_param(definition.params) is not None - rest_props = binding.take_rest(self.get_fields()) if has_rest else {} + rest_props = ( + binding.take_rest(self.get_fields(), definition.rest_target_field_names()) + if has_rest + else {} + ) super()._post_init(**binding.build_super_kwargs()) @@ -965,13 +987,22 @@ def add_event_trigger(self, js_prop_name: str, value: Any, args_spec: Any) -> No value=value, args_spec=args_spec, key=js_prop_name ) - def take_rest(self, component_fields: Mapping[str, Any]) -> dict[str, Any]: + def take_rest( + self, component_fields: Mapping[str, Any], rest_target_fields: set[str] + ) -> dict[str, Any]: rest: dict[str, Any] = {} + css: dict[str, Any] = {} for key in list(self.raw_kwargs): if key in component_fields or SpecialAttributes.is_special(key): continue - rest[format.to_camel_case(key)] = LiteralVar.create( - self.raw_kwargs.pop(key) + value = self.raw_kwargs.pop(key) + if key in rest_target_fields: + rest[format.to_camel_case(key)] = LiteralVar.create(value) + else: + css[key] = value + if css: + rest["css"] = LiteralVar.create( + convert_dict_to_style_and_format_emotion(css) ) return rest @@ -1047,11 +1078,14 @@ def _normalize_component_return(value: Any) -> Component | None: return None -def _lift_rest_props(component: Component) -> Component: +def _lift_rest_props(component: Component, rest_target_fields: set[str]) -> Component: """Convert RestProp children into special props. Args: component: The component tree to rewrite. + rest_target_fields: Accumulator that gathers the declared field names of + every component a ``RestProp`` is spread onto, so the call site can + tell forwarded props apart from CSS props. Returns: The rewritten component tree. @@ -1064,10 +1098,11 @@ def _lift_rest_props(component: Component) -> Component: for child in component.children: if isinstance(child, Bare) and isinstance(child.contents, RestProp): special_props.append(child.contents) + rest_target_fields.update(component.get_fields()) continue if isinstance(child, Component): - child = _lift_rest_props(child) + child = _lift_rest_props(child, rest_target_fields) rewritten_children.append(child) @@ -1267,13 +1302,17 @@ def _build_args_function( def _evaluate_component_body( - fn: Callable[..., Any], params: tuple[MemoParam, ...] + fn: Callable[..., Any], + params: tuple[MemoParam, ...], + rest_target_fields: set[str], ) -> Component: """Run a component memo's body and return its compiled component. Args: fn: The decorated function. params: The analyzed memo parameters. + rest_target_fields: Accumulator populated with the field names of the + component(s) the body spreads an ``rx.RestProp`` onto. Returns: The wrapped component the body returned. @@ -1288,7 +1327,7 @@ def _evaluate_component_body( "`rx.Component` or `rx.Var[rx.Component]`." ) raise TypeError(msg) - return _lift_rest_props(body) + return _lift_rest_props(body, rest_target_fields) def _evaluate_function_body( @@ -1325,12 +1364,16 @@ def _create_component_definition( TypeError: If the function does not return a component. """ params = _analyze_params(fn, for_component=True) + rest_target_fields: set[str] = set() return MemoComponentDefinition( fn=fn, python_name=fn.__name__, params=params, export_name=format.to_title_case(fn.__name__), - _component=_LazyBody.ready(_evaluate_component_body(fn, params)), + _component=_LazyBody.ready( + _evaluate_component_body(fn, params, rest_target_fields) + ), + _rest_target_fields=rest_target_fields, ) @@ -1881,15 +1924,17 @@ def memo(fn: Callable[..., Any]) -> _MemoComponentWrapper | _MemoFunctionWrapper # where the name resolves to ``wrapper`` (already bound by first use). definition: MemoComponentDefinition | MemoFunctionDefinition if is_component: + rest_target_fields: set[str] = set() definition = MemoComponentDefinition( fn=fn, python_name=fn.__name__, params=params, export_name=format.to_title_case(fn.__name__), _component=_LazyBody( - lambda: _evaluate_component_body(fn, params), + lambda: _evaluate_component_body(fn, params, rest_target_fields), placeholder=Fragment.create(), ), + _rest_target_fields=rest_target_fields, ) wrapper = _create_component_wrapper(definition) else: diff --git a/pyi_hashes.json b/pyi_hashes.json index dc601e746f3..7c6bf9b2717 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -1,124 +1,124 @@ { - "packages/reflex-components-code/src/reflex_components_code/code.pyi": "3b89c09ccc89eeeafe3e38ca539607a0", - "packages/reflex-components-code/src/reflex_components_code/shiki_code_block.pyi": "53c8f35514ebcf40ac5cf70c2342ac1c", - "packages/reflex-components-core/src/reflex_components_core/__init__.pyi": "d89c7cfa0920f664bc41d292ef882a64", - "packages/reflex-components-core/src/reflex_components_core/base/__init__.pyi": "d46e910c03263a8d3456d473de9e76c7", - "packages/reflex-components-core/src/reflex_components_core/base/app_wrap.pyi": "4a701f7eaf84ec041c8fd5c8af4e48ff", - "packages/reflex-components-core/src/reflex_components_core/base/body.pyi": "8550ac0cdb7f57b35dc639b605294b98", - "packages/reflex-components-core/src/reflex_components_core/base/document.pyi": "92be3177fff806cd44281cb912b21463", - "packages/reflex-components-core/src/reflex_components_core/base/error_boundary.pyi": "2bec3beeafea96787d10838d73e47bdc", - "packages/reflex-components-core/src/reflex_components_core/base/fragment.pyi": "04de5816bc98f33bbf14370af7f787b3", - "packages/reflex-components-core/src/reflex_components_core/base/link.pyi": "d39ded31424a9af8532b9d4eb0ce8abf", - "packages/reflex-components-core/src/reflex_components_core/base/meta.pyi": "c0e97a357ac2521c9b1c48b9368513e7", - "packages/reflex-components-core/src/reflex_components_core/base/script.pyi": "14cd6b846459f469168e78c305ee696a", - "packages/reflex-components-core/src/reflex_components_core/base/strict_mode.pyi": "135dd395a27f01be303b3879ece179c1", - "packages/reflex-components-core/src/reflex_components_core/core/__init__.pyi": "931b222daf3755f67281b6c4d7155f85", - "packages/reflex-components-core/src/reflex_components_core/core/auto_scroll.pyi": "2d83dc211884423496c31d372d3f3734", - "packages/reflex-components-core/src/reflex_components_core/core/banner.pyi": "473666c89f74a1fcd82f12cc2cd34f43", - "packages/reflex-components-core/src/reflex_components_core/core/clipboard.pyi": "21d51b69ab11279e864f7aca9307462f", - "packages/reflex-components-core/src/reflex_components_core/core/debounce.pyi": "86b2bea0aeec5334dbb64b3addae91df", - "packages/reflex-components-core/src/reflex_components_core/core/helmet.pyi": "cbbfc51195fb5cef671ff9ce65586d29", - "packages/reflex-components-core/src/reflex_components_core/core/html.pyi": "bedb5c94e690d50721ddc7bfdfbe8a25", - "packages/reflex-components-core/src/reflex_components_core/core/sticky.pyi": "a910d65f42f620531c959677d22977cb", - "packages/reflex-components-core/src/reflex_components_core/core/upload.pyi": "d903297d188fe6fe56a824127e0db925", - "packages/reflex-components-core/src/reflex_components_core/core/window_events.pyi": "3e39f7b3282570da5d2d3ac503d1f278", - "packages/reflex-components-core/src/reflex_components_core/datadisplay/__init__.pyi": "f46edcf2c5bd4b80894b4d0f623e059b", - "packages/reflex-components-core/src/reflex_components_core/el/__init__.pyi": "ca9bfe7ba432aa47d982a144ef374126", - "packages/reflex-components-core/src/reflex_components_core/el/element.pyi": "930b020d7c933ab0e29ba7971ba1abd2", - "packages/reflex-components-core/src/reflex_components_core/el/elements/__init__.pyi": "ded6fc0acc37c9260da5cfd5f4309079", - "packages/reflex-components-core/src/reflex_components_core/el/elements/base.pyi": "d075a1420b5a08cccd45aa58599ab3dc", - "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "42b0e8e04b79d7aa78d16b4f9ad39cef", - "packages/reflex-components-core/src/reflex_components_core/el/elements/inline.pyi": "c4085b9ee43847b6765cbf775341bc0b", - "packages/reflex-components-core/src/reflex_components_core/el/elements/media.pyi": "c2515454828415b78799f0ee0fb7a6af", - "packages/reflex-components-core/src/reflex_components_core/el/elements/metadata.pyi": "2d8d722c68f5a3082c0d3fa4883bb5c0", - "packages/reflex-components-core/src/reflex_components_core/el/elements/other.pyi": "da35b7e4a4496c7a84e79bc447b69ba1", - "packages/reflex-components-core/src/reflex_components_core/el/elements/scripts.pyi": "ddb26aa7e3f437579ac48dbe1da3bcfc", - "packages/reflex-components-core/src/reflex_components_core/el/elements/sectioning.pyi": "1f91a452bceebd410056e4fbdad0dfe9", - "packages/reflex-components-core/src/reflex_components_core/el/elements/tables.pyi": "7b4c2eaff78f6a412f804457570014c4", - "packages/reflex-components-core/src/reflex_components_core/el/elements/typography.pyi": "a3d0d5b0ad9d896261091603df6d2c03", - "packages/reflex-components-core/src/reflex_components_core/react_router/dom.pyi": "7617635157b816f70354e2db98b38642", - "packages/reflex-components-dataeditor/src/reflex_components_dataeditor/dataeditor.pyi": "5d12a0b0e9b4d76a9e4ba1d486094c1c", - "packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "2ce1c076ecf5c2fa4945b4abdbf2f91d", - "packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "2e1da186a37e2bb8a1d90e16ee9a63b5", - "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "79d0a59b1ba12a2f2c4a09fa6b5c776f", - "packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "eabf233471bc5b94084914f6f35ecd66", - "packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "6b84ff7659294b20d21b0ef7781531fa", - "packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "a77352f60fb6f4135b5d08a6e56efa6d", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "bbd4d1a4fa73275a882c33ba485d0165", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/accordion.pyi": "2639b56ce9ccb8b404c6dc12eaace19d", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/base.pyi": "c6500b29e90ca0c04e6bac65ad19936e", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/dialog.pyi": "e67999e6a7433d0eb8c18f4dd185761b", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/drawer.pyi": "172048f74ea5cb7b0e68829d5de3a1fa", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/form.pyi": "2a0611c1ffa91b4c2061ef3697b7ec75", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/progress.pyi": "849b07e27c3916bd24bb576b57e9e2c4", - "packages/reflex-components-radix/src/reflex_components_radix/primitives/slider.pyi": "2846f40bff377ee688d46055c4f7390a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/__init__.pyi": "6c7e1b9a0a7ca00f029bf6ba0c514fdb", - "packages/reflex-components-radix/src/reflex_components_radix/themes/base.pyi": "bf24641c7b44efcacaf67e2e49947c2c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/color_mode.pyi": "efcec0a98ceb064556bcbcd8718ac9cc", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/__init__.pyi": "758646fcd894b0acb09bfb6b97f2d475", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/alert_dialog.pyi": "c4fc4b724c07f7cce647e8c72046d01a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/aspect_ratio.pyi": "376e58873fa3431e178420d6c5806967", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/avatar.pyi": "ca8e7b05994c4bfb0e7f6ca611d48275", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/badge.pyi": "73ebf22c8fbb2d1388a3f1cd55eb1919", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/button.pyi": "3cd7cc1df19d44e14da3b291e4689ba2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/callout.pyi": "030a61bddc63006470b856fa2d29fe47", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/card.pyi": "88cb5e549b3a4e099fe4a0795a2d4977", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox.pyi": "cd5187687dc614b908bf6ac5216c56fa", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_cards.pyi": "f1632234039d89dc08a06e1fae7952d9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/checkbox_group.pyi": "2c6b06167afb6a3fad615ae689fc706c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/context_menu.pyi": "61f55df2ce771eb3d3d50b0441a6db76", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/data_list.pyi": "5891cd0930bcdbd55b37a6d945e99cd3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dialog.pyi": "6533a2e38a2cdfe8e0c798d975a566fe", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/dropdown_menu.pyi": "1368f79dd2a9d3e5d628ffb18d0f15fa", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/hover_card.pyi": "4566d59581a7bd7d870994a1a6267349", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/icon_button.pyi": "d16568436493dce1a35dd9b706dbb6ba", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/inset.pyi": "eaeb2399578e423ce7887d92fc276012", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/popover.pyi": "f0e2ce71c899e7029e0dffa9a88df1d2", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/progress.pyi": "e90d1f64301e17ca54a1edf2878bea9f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio.pyi": "e14c994517828f84d9fc9f85501eee7a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_cards.pyi": "45470676b80c15d722b142c27521a64f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/radio_group.pyi": "e933b49dab8027e1829cdd90ba99cc5d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/scroll_area.pyi": "b83dd81a37aaad75dcb6341e94e6d41f", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.pyi": "dfdde032961bda7905ef4256eb19152d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/select.pyi": "98818bc17dd54d4a0bfb48f7d971875a", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/separator.pyi": "b97507dd3c8ee6464b733fb61558c0ba", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/skeleton.pyi": "77e709d2f460b19617c9fc2e45537e18", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/slider.pyi": "27ae71e52b3a6abce5d94e83c354afb9", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/spinner.pyi": "a44624cea06503e76fbc1d90132faa72", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/switch.pyi": "ced70bf91fa0b598db0d1d976f2caeb7", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/table.pyi": "20b17107bb0ce5289a89b13fcef023e4", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tabs.pyi": "4bb81b1bbb020add0bff1a5cbd541442", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_area.pyi": "4eba432001b75e809105dde744cc987c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/text_field.pyi": "1adb24a4b4d8dde64c1d6bb19807675b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/components/tooltip.pyi": "7159fb41c6e8e5375d70e01b3db5878d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/__init__.pyi": "dcb8926703a1015583fc12daadb0ee21", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/base.pyi": "3722a5c6ed8cdd5f483b112b673a460d", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/box.pyi": "91c0636f8347bb3861b98679129054fe", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/center.pyi": "97a48bf51881c96c48230e97495ef526", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.pyi": "c1b4dd2f75d37b95ccce31d95bce339c", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/flex.pyi": "ff5f0a7671b3b52253d1e9c01091e383", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/grid.pyi": "4623cb3f6cacd49d9ace0f582eec6821", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/list.pyi": "8b7a051ef85c0e0c14a61ee3f50ca260", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/section.pyi": "9eedd4b6a85daee106ee4a4ca2e8b1a5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/spacer.pyi": "02655879b13b3edf07bc18f21cec2d39", - "packages/reflex-components-radix/src/reflex_components_radix/themes/layout/stack.pyi": "94d5d4aef75d03be26d07ee0bf20fac1", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/__init__.pyi": "a904755b3ecbd65b826328431342b8b5", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/blockquote.pyi": "377d220f6ecb0e734cfe4a8b7cc834da", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/code.pyi": "2bd706e29877dfdeda6ca46900f7414b", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/heading.pyi": "6691a55131a893676a68960cf64116e3", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/link.pyi": "e4d9e52ce7f2e97a4d2136eade8bf7cd", - "packages/reflex-components-radix/src/reflex_components_radix/themes/typography/text.pyi": "2f94a562aa24860cf6754c86128ffba1", - "packages/reflex-components-react-player/src/reflex_components_react_player/audio.pyi": "39e4144cef066bbff8c27b36a205a54b", - "packages/reflex-components-react-player/src/reflex_components_react_player/react_player.pyi": "86fc106181638c6a0a2a199332be817f", - "packages/reflex-components-react-player/src/reflex_components_react_player/video.pyi": "2682dc6e825d25307d8390d01e2bd653", - "packages/reflex-components-recharts/src/reflex_components_recharts/__init__.pyi": "359e123d9a046557ce05a96ce10313f5", - "packages/reflex-components-recharts/src/reflex_components_recharts/cartesian.pyi": "3485aaabbfd3ca89908ae19425c7297e", - "packages/reflex-components-recharts/src/reflex_components_recharts/charts.pyi": "280a7cd51298ee676f3d076104133f44", - "packages/reflex-components-recharts/src/reflex_components_recharts/general.pyi": "f5e3491c4e1f69ba89085682888888a9", - "packages/reflex-components-recharts/src/reflex_components_recharts/polar.pyi": "99ebcfc07868061bdc3c2010d85a153f", - "packages/reflex-components-recharts/src/reflex_components_recharts/recharts.pyi": "4f6c26f8c76543cc41e2b9dc400ece8a", - "packages/reflex-components-sonner/src/reflex_components_sonner/toast.pyi": "58521fcd1b514804f534d97624e82c9a", - "reflex/__init__.pyi": "56385a4f0d9431eb0056dbc5553a58f9", - "reflex/components/__init__.pyi": "9facd05a776d0641432696bbf8e34388", - "reflex/experimental/memo.pyi": "58d97de180cc34a8aa605cd76d97ee57" + "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": "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": "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": "b8e4b197e7678faa653d9a2f7da10591", + "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": "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": "d89d4cf5fa68f7607cc613ba862dbc33", + "packages/reflex-components-core/src/reflex_components_core/el/elements/forms.pyi": "0929908170b04328b23e2343493dbd14", + "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": "cbac4e023ad9b9923736908600d416f6", + "packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "62a1b311ed239ff1dc943478ae2b7c7d", + "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": "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": "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": "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": "75f5da11fe90491751d8ebcb3d1332a1", + "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": "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": "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": "674cc55e646deb97c0e414e1d0e850ef", + "reflex/components/__init__.pyi": "f39a2af77f438fa243c58c965f19d42e", + "reflex/experimental/memo.pyi": "1398d199d6a6eb62daef9aa1a7cd1eea" } diff --git a/tests/integration/test_memo.py b/tests/integration/test_memo.py index 4a8524d6e2f..7dbbdd41cbb 100644 --- a/tests/integration/test_memo.py +++ b/tests/integration/test_memo.py @@ -73,6 +73,7 @@ def index() -> rx.Component: value=formatted_price, id="summary-card", class_name="forwarded-summary-card", + font_weight="bold", ), ) @@ -120,6 +121,8 @@ def test_memo_app(memo_app: AppHarness): summary_card = driver.find_element(By.ID, "summary-card") assert "forwarded-summary-card" in (summary_card.get_attribute("class") or "") + # CSS props forwarded through `rx.RestProp` are applied as styles (ENG-9676). + assert summary_card.value_of_css_property("font-weight") == "700" assert driver.find_element(By.ID, "summary-title").text == "Current Price" assert ( driver.find_element(By.ID, "summary-child").text diff --git a/tests/units/components/test_memo.py b/tests/units/components/test_memo.py index 9276bf248c2..f3db4aed41f 100644 --- a/tests/units/components/test_memo.py +++ b/tests/units/components/test_memo.py @@ -99,7 +99,9 @@ def my_card( assert isinstance(component, MemoComponent) assert len(component.children) == 2 - assert component.get_props() == ("title", "foo") + # `foo` is not a field of the rest target (`Box`), so it is routed into + # `css` just like `rx.box(foo="extra")` would; `className` is a base field. + assert component.get_props() == ("title", "css") assert type(component) is type(component_again) assert type(component).tag == "MyCard" assert type(component).get_fields()["tag"].default == "MyCard" @@ -107,7 +109,7 @@ def my_card( rendered = component.render() assert rendered["name"] == "MyCard" assert 'title:"Hello"' in rendered["props"] - assert 'foo:"extra"' in rendered["props"] + assert 'css:({ ["foo"] : "extra" })' in rendered["props"] assert 'className:"extra"' in rendered["props"] definition = MEMOS["MyCard"] @@ -441,6 +443,45 @@ def rest_card(rest: rx.RestProp, *, title: rx.Var[str]) -> rx.Component: assert "{...rest}" in code +def test_memo_css_props_forwarded_via_rest_prop_become_css(): + """CSS props forwarded through an ``rx.RestProp`` compile to ``css`` (ENG-9676). + + A normal component folds any kwarg that is not a declared field of the target + into emotion ``css``. A memo forwarding via ``rx.RestProp`` must do the same: + ``font_weight`` is not a ``Text`` field, so it has to reach the root as ``css`` + rather than a raw ``fontWeight`` plain prop the target silently drops. + """ + + @rx.memo + def styled_text(rest: rx.RestProp) -> rx.Component: + return rx.text("Foo", rest) + + rendered = str(styled_text(font_weight="bold", class_name="c")) + # Matches the shape of `rx.text("Bar", font_weight="bold")`. + assert "css:" in rendered + assert '["fontWeight"] : "bold"' in rendered + # Not forwarded as a plain prop the target would ignore. + assert 'fontWeight:"bold"' not in rendered + # A genuine base `Component` field stays a normal plain prop. + assert 'className:"c"' in rendered + + +def test_memo_rest_prop_keeps_real_target_props_as_props(): + """A prop that IS a declared field of the rest target stays a plain prop (ENG-9676). + + Guards the shadowing case: ``weight`` is a real ``Text`` prop, so it must be + forwarded normally and never reclassified into ``css``. + """ + + @rx.memo + def styled_text(rest: rx.RestProp) -> rx.Component: + return rx.text("Foo", rest) + + rendered = str(styled_text(weight="bold")) + assert 'weight:"bold"' in rendered + assert "css:" not in rendered + + def test_memo_component_still_rejects_unknown_props_without_rest(): """Props that are not base ``Component`` fields still raise without a ``RestProp``.""" @@ -1364,11 +1405,15 @@ def test_bind_children_and_rest_are_noops_at_the_param_level(): assert binding._event_triggers == {} -def test_take_rest_sweeps_unconsumed_keys_into_camel_cased_dict(): - """binding.take_rest collects every leftover kwarg not on the Component.""" - binding = _MemoCallBinding({"foo_bar": "x", "class_name": "y"}) - rest = binding.take_rest(component_fields={}) - assert set(rest) == {"fooBar", "className"} +def test_take_rest_forwards_target_fields_and_routes_remainder_to_css(): + """take_rest forwards declared target fields and sweeps the rest into ``css``. + + Keys that are fields of the rest target are forwarded as camelCased plain + props; everything else is a CSS prop, collected under a single ``css`` key. + """ + binding = _MemoCallBinding({"foo_bar": "x", "font_weight": "bold"}) + rest = binding.take_rest(component_fields={}, rest_target_fields={"foo_bar"}) + assert set(rest) == {"fooBar", "css"} assert binding.raw_kwargs == {}