diff --git a/pyi_hashes.json b/pyi_hashes.json index 02116a82bc8..a9efd420879 100644 --- a/pyi_hashes.json +++ b/pyi_hashes.json @@ -3,13 +3,13 @@ "reflex/components/__init__.pyi": "76ba0a12cd3a7ba5ab6341a3ae81551f", "reflex/components/base/__init__.pyi": "e9aaf47be1e1977eacee97b880c8f7de", "reflex/components/base/app_wrap.pyi": "387fc7a0c2da8760d9449e2893e44eec", - "reflex/components/base/body.pyi": "2cc870cec4b1c28081dd40467752c2b7", + "reflex/components/base/body.pyi": "2d16002f24c8ee0007b46ff2bf1f2c78", "reflex/components/base/document.pyi": "30377cdfb02b564f8de29b0473d2346c", "reflex/components/base/error_boundary.pyi": "c56b591d14a92b99a1e97e04afe167d7", "reflex/components/base/fragment.pyi": "603ee8e03af88d4a8ff6bc1fbce4e022", "reflex/components/base/head.pyi": "893047aa32da553711db8f1345adb6b0", - "reflex/components/base/link.pyi": "396488afa3b7a5b0d0e6c5e89159f857", - "reflex/components/base/meta.pyi": "bc4b4fda6f022a517de339ffdd667e3b", + "reflex/components/base/link.pyi": "e96179dc7823f354fb73a6c03e31028c", + "reflex/components/base/meta.pyi": "da52c3212fac6b50560863146a7afcc3", "reflex/components/base/script.pyi": "530cf8f47eb90082bf65942e8b5d745f", "reflex/components/base/strict_mode.pyi": "d972e0ff2a6f961e7df90fc27b8bb51b", "reflex/components/core/__init__.pyi": "44bcee7bc4e27e2f4f4707b843acf291", @@ -26,13 +26,13 @@ "reflex/components/datadisplay/dataeditor.pyi": "cb03d732e2fe771a8d46c7bcda671f92", "reflex/components/datadisplay/shiki_code_block.pyi": "87db7639bfa5cd53e1709e1363f93278", "reflex/components/el/__init__.pyi": "09042a2db5e0637e99b5173430600522", - "reflex/components/el/element.pyi": "06ac2213b062119323291fa66a1ac19e", + "reflex/components/el/element.pyi": "ea6b33a8545c2c845dc6c30ff1c872a4", "reflex/components/el/elements/__init__.pyi": "280ed457675f3720e34b560a3f617739", "reflex/components/el/elements/base.pyi": "6e533348b5e1a88cf62fbb5a38dbd795", "reflex/components/el/elements/forms.pyi": "161f1ef847e5da8755528a7977fdcf53", "reflex/components/el/elements/inline.pyi": "33d9d860e75dd8c4769825127ed363bb", "reflex/components/el/elements/media.pyi": "addd6872281d65d44a484358b895432f", - "reflex/components/el/elements/metadata.pyi": "974a86d9f0662f6fc15a5bb4b3a87862", + "reflex/components/el/elements/metadata.pyi": "a5b9b30c4649e88aa26a1a5609fc86ef", "reflex/components/el/elements/other.pyi": "995a4fbf10bfdb7f48808210dfe413bd", "reflex/components/el/elements/scripts.pyi": "cd5bd53c3a6b016fbb913aff36d63344", "reflex/components/el/elements/sectioning.pyi": "65aa53b1372598ec1785616cb7016032", @@ -40,7 +40,7 @@ "reflex/components/el/elements/typography.pyi": "00088c9c1b68a14e5a41d837e8fdf542", "reflex/components/gridjs/datatable.pyi": "3db3f994640c19be5c3fa2983f71de56", "reflex/components/lucide/icon.pyi": "a5521a8baf8d2d7281e3fdfe6ce7073b", - "reflex/components/markdown/markdown.pyi": "6b268afa879e33abf651bda56be5065e", + "reflex/components/markdown/markdown.pyi": "1fc31d2652d3ff015c6da2c7cbab716a", "reflex/components/moment/moment.pyi": "6dd0c7cee5f0f29bc11d830c697d7f92", "reflex/components/next/base.pyi": "14aafd5b018a4bc9748a3c9980fcfe3e", "reflex/components/next/image.pyi": "3a0d1970e69144e9c6806e68ab99f181", @@ -101,7 +101,7 @@ "reflex/components/radix/themes/layout/container.pyi": "4020c3dca660027b84d11cc4198393c4", "reflex/components/radix/themes/layout/flex.pyi": "f814281a5635ad43dd1df23f8e356c66", "reflex/components/radix/themes/layout/grid.pyi": "6062188367a2c253f014f916197c963d", - "reflex/components/radix/themes/layout/list.pyi": "0e91d3f1c82c9094f328e5b8ecd2f60a", + "reflex/components/radix/themes/layout/list.pyi": "930009f82662686841e9ce97bfd4a1ea", "reflex/components/radix/themes/layout/section.pyi": "41895910072e023ed0fef6a8ad956046", "reflex/components/radix/themes/layout/spacer.pyi": "029eb0eaa731bcdff7c496e0437e22b1", "reflex/components/radix/themes/layout/stack.pyi": "3b0da99b00c826d087ed89fc67c595c1", diff --git a/reflex/.templates/jinja/web/pages/_app.js.jinja2 b/reflex/.templates/jinja/web/pages/_app.js.jinja2 index 5a10be30696..1b299d395d7 100644 --- a/reflex/.templates/jinja/web/pages/_app.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/_app.js.jinja2 @@ -22,7 +22,7 @@ function AppWrap({children}) { {{ renderHooks(hooks) }} return ( - {{utils.render(render, indent_width=0)}} + {{utils.render(render)}} ) } @@ -37,15 +37,15 @@ export default function MyApp({ Component, pageProps }) { window["__reflex"] = windowImports; }, []); return ( - - - - - - - - - + jsx(ThemeProvider, {defaultTheme:defaultColorMode,attribute:"class"}, + jsx(StateProvider, {}, + jsx(EventLoopProvider, {}, + jsx(AppWrap, {}, + jsx(Component, pageProps) + ) + ) + ) + ) ); } diff --git a/reflex/.templates/jinja/web/pages/_document.js.jinja2 b/reflex/.templates/jinja/web/pages/_document.js.jinja2 index 266c0d5a543..b17731ee0fe 100644 --- a/reflex/.templates/jinja/web/pages/_document.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/_document.js.jinja2 @@ -3,7 +3,7 @@ {% block export %} export default function Document() { return ( - {{utils.render(document, indent_width=0)}} + {{utils.render(document)}} ) } {% endblock %} diff --git a/reflex/.templates/jinja/web/pages/index.js.jinja2 b/reflex/.templates/jinja/web/pages/index.js.jinja2 index 5551ad5fcc2..98a2aefd6df 100644 --- a/reflex/.templates/jinja/web/pages/index.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/index.js.jinja2 @@ -12,7 +12,7 @@ export default function Component() { {{ renderHooks(hooks)}} return ( - {{utils.render(render, indent_width=0)}} + {{utils.render(render)}} ) } {% endblock %} diff --git a/reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 b/reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 index 208a5755f94..f170ce92057 100644 --- a/reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 @@ -6,6 +6,6 @@ export function {{tag_name}} () { {{ renderHooksWithMemo(all_hooks, memo_trigger_hooks) }} return ( - {{utils.render(component.render(), indent_width=0)}} + {{utils.render(component.render())}} ) } diff --git a/reflex/.templates/jinja/web/pages/utils.js.jinja2 b/reflex/.templates/jinja/web/pages/utils.js.jinja2 index c883dadcb62..14c62f18dd8 100644 --- a/reflex/.templates/jinja/web/pages/utils.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/utils.js.jinja2 @@ -1,46 +1,37 @@ {# Rendering components recursively. #} {# Args: #} {# component: component dictionary #} -{# indent_width: indent width #} -{% macro render(component, indent_width=0) %} -{% filter indent(width=indent_width) %} - {%- if component is not mapping %} - {{- component }} - {%- elif "iterable" in component %} - {{- render_iterable_tag(component) }} - {%- elif component.name == "match"%} - {{- render_match_tag(component) }} - {%- elif "cond" in component %} - {{- render_condition_tag(component) }} - {%- elif component.children|length %} - {{- render_tag(component) }} - {%- else %} - {{- render_self_close_tag(component) }} - {%- endif %} -{% endfilter %} +{% macro render(component) %} +{%- if component is not mapping %}{{ component }} +{%- elif "iterable" in component %}{{ render_iterable_tag(component) }} +{%- elif component.name == "match"%}{{ render_match_tag(component) }} +{%- elif "cond" in component %}{{ render_condition_tag(component) }} +{%- elif component.children|length %}{{ render_tag(component) }} +{%- else %}{{ render_self_close_tag(component) }} +{%- endif %} {% endmacro %} {# Rendering self close tag. #} {# Args: #} {# component: component dictionary #} {% macro render_self_close_tag(component) %} -{%- if component.name|length %} -<{{ component.name }} {{- render_props(component.props) }}{% if component.autofocus %} ref={focusRef} {% endif %}/> -{%- else %} - {{- component.contents }} -{%- endif %} +{% if component.name|length %} +jsx({{ component.name }},{{ render_props(component.props) }},{{ component.contents }}) +{% elif component.contents|length -%}{{ component.contents }} +{% else %}"" +{% endif %} {% endmacro %} {# Rendering close tag with args and props. #} {# Args: #} {# component: component dictionary #} {% macro render_tag(component) %} -<{{component.name}} {{- render_props(component.props) }}> -{{ component.contents }} -{% for child in component.children %} -{{ render(child) }} -{% endfor %} - +jsx( +{% if component.name|length %}{{ component.name }}{% else %}Fragment{% endif %}, +{{ render_props(component.props) }}, +{% if component.contents|length %}{{ component.contents }},{% endif %} +{% for child in component.children %}{% if child is mapping or child|length %}{{ render(child) }},{% endif %}{% endfor %} +) {%- endmacro %} @@ -48,11 +39,7 @@ {# Args: #} {# component: component dictionary #} {% macro render_condition_tag(component) %} -{ {{- component.cond_state }} ? ( - {{ render(component.true_value) }} -) : ( - {{ render(component.false_value) }} -)} +({{ component.cond_state }} ? ({{ render(component.true_value) }}) : ({{ render(component.false_value) }})) {%- endmacro %} @@ -60,57 +47,33 @@ {# Args: #} {# component: component dictionary #} {% macro render_iterable_tag(component) %} -<>{ {{ component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => ( - {% for child in component.children %} - {{ render(child) }} - {% endfor %} -))} +{{ component.iterable_state }}.map(({{ component.arg_name }},{{ component.arg_index }})=>({% for child in component.children %}{{ render(child) }}{% endfor %})) {%- endmacro %} {# Rendering props of a component. #} {# Args: #} {# component: component dictionary #} -{% macro render_props(props) %} -{% if props|length %} {{ props|join(" ") }}{% endif %} -{% endmacro %} +{% macro render_props(props) %}{{ "{" }}{% if props|length %}{{ props|join(",") }}{% endif %}{{ "}" }}{% endmacro %} {# Rendering Match component. #} {# Args: #} {# component: component dictionary #} {% macro render_match_tag(component) %} -{ - (() => { - switch (JSON.stringify({{ component.cond._js_expr }})) { - {% for case in component.match_cases %} - {% for condition in case[:-1] %} - case JSON.stringify({{ condition._js_expr }}): - {% endfor %} - return {{ render(case[-1]) }}; - break; - {% endfor %} - default: - return {{ render(component.default) }}; - break; - } - })() - } -{%- endmacro %} - - -{# Rendering content with args. #} -{# Args: #} -{# component: component dictionary #} -{% macro render_arg_content(component) %} -{% filter indent(width=2) %} -{# no string below for a line break #} - -{({ {{component.args|join(", ")}} }) => ( - {% for child in component.children %} - {{ render(child) }} +(() => { + switch (JSON.stringify({{ component.cond._js_expr }})) { + {% for case in component.match_cases %} + {% for condition in case[:-1] %} + case JSON.stringify({{ condition._js_expr }}): + {% endfor %} + return {{ render(case[-1]) }}; + break; {% endfor %} -)} -{% endfilter %} + default: + return {{ render(component.default) }}; + break; + } +})() {% endmacro %} diff --git a/reflex/.templates/jinja/web/utils/context.js.jinja2 b/reflex/.templates/jinja/web/utils/context.js.jinja2 index d138d2c12be..08d3e7f16d6 100644 --- a/reflex/.templates/jinja/web/utils/context.js.jinja2 +++ b/reflex/.templates/jinja/web/utils/context.js.jinja2 @@ -1,4 +1,4 @@ -import { createContext, useContext, useMemo, useReducer, useState } from "react" +import { createContext, useContext, useMemo, useReducer, useState, createElement } from "react" import { applyDelta, Event, hydrateClientStorage, useEventLoop, refs } from "$/utils/state.js" {% if initial_state %} @@ -77,11 +77,7 @@ export function UploadFilesProvider({ children }) { delete newFilesById[id] return newFilesById }) - return ( - - {children} - - ) + return createElement(UploadFilesContext, {value:[filesById, setFilesById]}, children); } export function EventLoopProvider({ children }) { @@ -91,11 +87,7 @@ export function EventLoopProvider({ children }) { initialEvents, clientStorage, ) - return ( - - {children} - - ) + return createElement(EventLoopContext, {value:[addEvents, connectErrors]}, children); } export function StateProvider({ children }) { @@ -112,13 +104,9 @@ export function StateProvider({ children }) { return ( {% for state_name in initial_state %} - - {% endfor %} - - {children} - - {% for state_name in initial_state|reverse %} - + createElement(StateContexts.{{state_name|var_name}},{value: {{state_name|var_name}}}, {% endfor %} + createElement(DispatchContext.Provider, {value: dispatchers}, children), + {% for state_name in initial_state|reverse %}){% endfor %} ) } diff --git a/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js b/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js index 0849403be8c..25a5a5b68b0 100644 --- a/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +++ b/reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js @@ -1,5 +1,5 @@ import { useTheme } from "next-themes"; -import { useRef, useEffect, useState } from "react"; +import { useRef, useEffect, useState, createElement } from "react"; import { ColorModeContext, defaultColorMode, @@ -50,11 +50,11 @@ export default function RadixThemesColorModeProvider({ children }) { } setTheme(mode); }; - return ( - - {children} - + return createElement( + ColorModeContext, + { + value: { rawColorMode, resolvedColorMode, toggleColorMode, setColorMode }, + }, + children, ); } diff --git a/reflex/.templates/web/components/shiki/code.js b/reflex/.templates/web/components/shiki/code.js index b2e369fd6af..8d721dd8a29 100644 --- a/reflex/.templates/web/components/shiki/code.js +++ b/reflex/.templates/web/components/shiki/code.js @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, createElement } from "react"; import { codeToHtml } from "shiki"; /** @@ -33,7 +33,8 @@ export function Code({ } fetchCode(); }, [code, language, theme, transformers, decorations]); - return ( -
- ); + return createElement("div", { + dangerouslySetInnerHTML: { __html: codeResult }, + ...divProps, + }); } diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 051914c4ea2..cf574933514 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -30,6 +30,13 @@ from reflex.vars.base import LiteralVar, Var +def _apply_common_imports( + imports: dict[str, list[ImportVar]], +): + imports.setdefault("@emotion/react", []).append(ImportVar("jsx")) + imports.setdefault("react", []).append(ImportVar("Fragment")) + + def _compile_document_root(root: Component) -> str: """Compile the document root. @@ -39,8 +46,10 @@ def _compile_document_root(root: Component) -> str: Returns: The compiled document root. """ + document_root_imports = root._get_all_imports() + _apply_common_imports(document_root_imports) return templates.DOCUMENT_ROOT.render( - imports=utils.compile_imports(root._get_all_imports()), + imports=utils.compile_imports(document_root_imports), document=root.render(), ) @@ -74,8 +83,11 @@ def _compile_app(app_root: Component) -> str: (_normalize_library_name(name), name) for name in bundled_libraries ] + app_root_imports = app_root._get_all_imports() + _apply_common_imports(app_root_imports) + return templates.APP_ROOT.render( - imports=utils.compile_imports(app_root._get_all_imports()), + imports=utils.compile_imports(app_root_imports), custom_codes=app_root._get_all_custom_code(), hooks=app_root._get_all_hooks(), window_libraries=window_libraries, @@ -143,6 +155,7 @@ def _compile_page( The compiled component. """ imports = component._get_all_imports() + _apply_common_imports(imports) imports = utils.compile_imports(imports) # Compile the code to render the component. @@ -325,7 +338,7 @@ def _compile_components( """ imports = { "react": [ImportVar(tag="memo")], - f"$/{constants.Dirs.STATE_PATH}": [ImportVar(tag="E"), ImportVar(tag="isTrue")], + f"$/{constants.Dirs.STATE_PATH}": [ImportVar(tag="isTrue")], } component_renders = [] @@ -335,6 +348,8 @@ def _compile_components( component_renders.append(component_render) imports = utils.merge_imports(imports, component_imports) + _apply_common_imports(imports) + dynamic_imports = { comp_import: None for comp_render in component_renders @@ -427,6 +442,8 @@ def get_shared_components_recursive(component: BaseComponent): all_imports.pop( f"$/{constants.Dirs.UTILS}/{constants.PageNames.STATEFUL_COMPONENTS}", None ) + if rendered_components: + _apply_common_imports(all_imports) return templates.STATEFUL_COMPONENTS.render( imports=utils.compile_imports(all_imports), diff --git a/reflex/components/base/bare.py b/reflex/components/base/bare.py index 5e9167dfc61..f4c10f0b43b 100644 --- a/reflex/components/base/bare.py +++ b/reflex/components/base/bare.py @@ -169,11 +169,14 @@ def _get_all_refs(self) -> set[str]: return refs def _render(self) -> Tag: - if isinstance(self.contents, Var): - if isinstance(self.contents, (BooleanVar, ObjectVar)): - return Tagless(contents=f"{{{self.contents.to_string()!s}}}") - return Tagless(contents=f"{{{self.contents!s}}}") - return Tagless(contents=str(self.contents)) + contents = ( + Var.create(self.contents) + if not isinstance(self.contents, Var) + else self.contents + ) + if isinstance(contents, (BooleanVar, ObjectVar)): + return Tagless(contents=f"{contents.to_string()!s}") + return Tagless(contents=f"{contents!s}") def _add_style_recursive( self, style: ComponentStyle, theme: Component | None = None diff --git a/reflex/components/base/body.py b/reflex/components/base/body.py index ee38cb5d407..2327aa2d366 100644 --- a/reflex/components/base/body.py +++ b/reflex/components/base/body.py @@ -1,9 +1,7 @@ """Display the page body.""" -from reflex.components.component import Component +from reflex.components.el import elements -class Body(Component): +class Body(elements.Body): """A body component.""" - - tag = "body" diff --git a/reflex/components/base/link.py b/reflex/components/base/link.py index 3ea34f2122b..c5c9222fd13 100644 --- a/reflex/components/base/link.py +++ b/reflex/components/base/link.py @@ -1,10 +1,10 @@ """Display the title of the current page.""" -from reflex.components.component import Component +from reflex.components.el.elements.base import BaseHTML from reflex.vars.base import Var -class RawLink(Component): +class RawLink(BaseHTML): """A component that displays the title of the current page.""" tag = "link" @@ -16,7 +16,7 @@ class RawLink(Component): rel: Var[str] -class ScriptTag(Component): +class ScriptTag(BaseHTML): """A script tag with the specified type and source.""" tag = "script" diff --git a/reflex/components/base/meta.py b/reflex/components/base/meta.py index 174a7567f9f..9f61b81ce53 100644 --- a/reflex/components/base/meta.py +++ b/reflex/components/base/meta.py @@ -3,14 +3,12 @@ from __future__ import annotations from reflex.components.base.bare import Bare -from reflex.components.component import Component +from reflex.components.el import elements -class Title(Component): +class Title(elements.Title): """A component that displays the title of the current page.""" - tag = "title" - def render(self) -> dict: """Render the title component. @@ -26,11 +24,9 @@ def render(self) -> dict: return super().render() -class Meta(Component): +class Meta(elements.Meta): """A component that displays metadata for the current page.""" - tag = "meta" - # The description of character encoding. char_set: str | None = None @@ -47,14 +43,14 @@ class Meta(Component): http_equiv: str | None = None -class Description(Meta): +class Description(elements.Meta): """A component that displays the title of the current page.""" # The type of the description. name: str | None = "description" -class Image(Meta): +class Image(elements.Meta): """A component that displays the title of the current page.""" # The type of the image. diff --git a/reflex/components/component.py b/reflex/components/component.py index 9150e580140..23d487d1502 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -268,6 +268,9 @@ class Component(BaseComponent, ABC): # The alias for the tag. alias: str | None = pydantic.v1.Field(default_factory=lambda: None) + # Whether the component is a global scope tag. True for tags like `html`, `head`, `body`. + _is_tag_in_global_scope: ClassVar[bool] = False + # Whether the import is default or named. is_default: bool | None = pydantic.v1.Field(default_factory=lambda: False) @@ -689,9 +692,14 @@ def _render(self, props: dict[str, Any] | None = None) -> Tag: Returns: The tag to render. """ + # Create the base tag. + name = (self.tag if not self.alias else self.alias) or "" + if self._is_tag_in_global_scope and self.library is None: + name = '"' + name + '"' + # Create the base tag. tag = Tag( - name=(self.tag if not self.alias else self.alias) or "", + name=name, special_props=self.special_props, ) @@ -2570,12 +2578,12 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) -> special_props = [] for prop_str in tag["props"]: - if "=" not in prop_str: + if ":" not in prop_str: special_props.append(Var(prop_str).to(ObjectVar)) continue - prop = prop_str.index("=") + prop = prop_str.index(":") key = prop_str[:prop] - value = prop_str[prop + 2 : -1] + value = prop_str[prop + 1 :] props[key] = value props = LiteralObjectVar.create( @@ -2585,19 +2593,11 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) -> for prop in special_props: props = props.merge(prop) - contents = tag["contents"][1:-1] if tag["contents"] else None + contents = tag["contents"] if tag["contents"] else None raw_tag_name = tag.get("name") tag_name = Var(raw_tag_name or "Fragment") - tag_name = ( - LiteralStringVar.create(raw_tag_name) - if raw_tag_name - and raw_tag_name.split(".")[0] not in imported_names - and raw_tag_name.lower() == raw_tag_name - else tag_name - ) - return FunctionStringVar.create( "jsx", ).call( @@ -2642,23 +2642,9 @@ def _cached_get_all_var_data(self) -> VarData | None: """ return VarData.merge( self._var_data, - VarData( - imports={ - "@emotion/react": [ - ImportVar(tag="jsx"), - ], - } - ), VarData( imports=self._var_value._get_all_imports(), ), - VarData( - imports={ - "react": [ - ImportVar(tag="Fragment"), - ], - } - ), ) def __hash__(self) -> int: diff --git a/reflex/components/core/upload.py b/reflex/components/core/upload.py index 4b7be312cc5..0e75bcb3ea1 100644 --- a/reflex/components/core/upload.py +++ b/reflex/components/core/upload.py @@ -361,7 +361,7 @@ def create(cls, *children, **props) -> Component: upload = Input.create(type="file") upload.special_props = [ Var( - _js_expr=f"{{...{input_props_unique_name}()}}", + _js_expr=f"{input_props_unique_name}()", _var_type=None, _var_data=var_data, ) @@ -375,7 +375,7 @@ def create(cls, *children, **props) -> Component: ) zone.special_props = [ Var( - _js_expr=f"{{...{root_props_unique_name}()}}", + _js_expr=f"{root_props_unique_name}()", _var_type=None, _var_data=var_data, ) diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index d77a16dfe7e..b2498b8418d 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -72,7 +72,7 @@ def make_component(component: Component) -> str: The generated code """ # Causes a circular import, so we import here. - from reflex.compiler import templates, utils + from reflex.compiler import compiler, templates, utils from reflex.components.base.bare import Bare component = Bare.create(Var.create(component)) @@ -97,8 +97,11 @@ def make_component(component: Component) -> str: libs_in_window = bundled_libraries + component_imports = component._get_all_imports() + compiler._apply_common_imports(component_imports) + imports = {} - for lib, names in component._get_all_imports().items(): + for lib, names in component_imports.items(): formatted_lib_name = format_library_name(lib) if ( not lib.startswith((".", "/", "$/")) diff --git a/reflex/components/el/element.py b/reflex/components/el/element.py index c9a58b1f6f1..7232ee38f17 100644 --- a/reflex/components/el/element.py +++ b/reflex/components/el/element.py @@ -1,11 +1,15 @@ """Base class definition for raw HTML elements.""" +from typing import ClassVar + from reflex.components.component import Component class Element(Component): """The base class for all raw HTML elements.""" + _is_tag_in_global_scope: ClassVar[bool] = True + def __eq__(self, other: object): """Two elements are equal if they have the same tag. diff --git a/reflex/components/el/elements/metadata.py b/reflex/components/el/elements/metadata.py index 98aeaeecb55..bc961ce14da 100644 --- a/reflex/components/el/elements/metadata.py +++ b/reflex/components/el/elements/metadata.py @@ -89,7 +89,7 @@ class StyleEl(Element): media: Var[str] - special_props: list[Var] = [Var(_js_expr="suppressHydrationWarning")] + suppress_hydration_warning: Var[bool] = Var.create(True) base = Base.create diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index b410603a076..36c559516ae 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -18,8 +18,8 @@ # Special vars used in the component map. _CHILDREN = Var(_js_expr="children", _var_type=str) -_PROPS = Var(_js_expr="...props") -_PROPS_IN_TAG = Var(_js_expr="{...props}") +_PROPS = Var(_js_expr="props") +_PROPS_SPREAD = Var(_js_expr="...props") _MOCK_ARG = Var(_js_expr="", _var_type=str) _LANGUAGE = Var(_js_expr="_language", _var_type=str) @@ -128,7 +128,7 @@ def get_fn_args(cls) -> Sequence[str]: Returns: The function arguments as a list of strings. """ - return ["node", _CHILDREN._js_expr, _PROPS._js_expr] + return ["node", _CHILDREN._js_expr, _PROPS_SPREAD._js_expr] @classmethod def get_fn_body(cls) -> Var: @@ -297,7 +297,7 @@ def _get_inline_code_fn_var(self) -> Var: "inline", "className", _CHILDREN._js_expr, - _PROPS._js_expr, + _PROPS_SPREAD._js_expr, ), fn_body=Var(_js_expr=formatted_code), explicit_return=True, @@ -321,7 +321,7 @@ def get_component(self, tag: str, **props) -> Component: if tag not in self.component_map: raise ValueError(f"No markdown component found for tag: {tag}.") - special_props = [_PROPS_IN_TAG] + special_props = [_PROPS] children = [ _CHILDREN if tag != "codeblock" @@ -338,9 +338,8 @@ def get_component(self, tag: str, **props) -> Component: special_props = [] # If the children are set as a prop, don't pass them as children. - children_prop = props.pop("children", None) + children_prop = props.get("children") if children_prop is not None: - special_props.append(Var(_js_expr=f"children={{{children_prop!s}}}")) children = [] # Get the component. component = self.component_map[tag](*children, **props).set( diff --git a/reflex/components/plotly/plotly.py b/reflex/components/plotly/plotly.py index b0eefeb89bd..e05b1fc5969 100644 --- a/reflex/components/plotly/plotly.py +++ b/reflex/components/plotly/plotly.py @@ -252,7 +252,7 @@ def _render(self): ) else: # Spread the figure dict over props, nothing to merge. - tag.special_props.append(Var(_js_expr=f"{{...{figure!s}}}")) + tag.special_props.append(Var(_js_expr=f"{figure!s}")) return tag diff --git a/reflex/components/radix/themes/layout/list.py b/reflex/components/radix/themes/layout/list.py index 19f0d42f5ff..e976b7146d1 100644 --- a/reflex/components/radix/themes/layout/list.py +++ b/reflex/components/radix/themes/layout/list.py @@ -5,8 +5,9 @@ from collections.abc import Iterable from typing import Any, Literal -from reflex.components.component import Component, ComponentNamespace +from reflex.components.component import ComponentNamespace from reflex.components.core.foreach import Foreach +from reflex.components.el.elements.base import BaseHTML from reflex.components.el.elements.typography import Li, Ol, Ul from reflex.components.lucide.icon import Icon from reflex.components.markdown.markdown import MarkdownComponentMap @@ -38,7 +39,7 @@ ] -class BaseList(Component, MarkdownComponentMap): +class BaseList(BaseHTML, MarkdownComponentMap): """Base class for ordered and unordered lists.""" tag = "ul" diff --git a/reflex/utils/format.py b/reflex/utils/format.py index d0492c12adb..54c404f1124 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -439,12 +439,19 @@ def format_props(*single_props, **key_value_props) -> list[str]: from reflex.vars.base import LiteralVar, Var return [ - ( - f"{name}={{{format_prop(prop if isinstance(prop, Var) else LiteralVar.create(prop))}}}" + ":".join( + [ + str(name if "-" not in name else LiteralVar.create(name)), + str( + format_prop( + prop if isinstance(prop, Var) else LiteralVar.create(prop) + ) + ), + ] ) for name, prop in sorted(key_value_props.items()) if prop is not None - ] + [(f"{LiteralVar.create(prop)!s}") for prop in single_props] + ] + [(f"...{LiteralVar.create(prop)!s}") for prop in single_props] def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]: diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index cd744860fb6..d42e9c5c380 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -50,6 +50,7 @@ "tag", "is_default", "special_props", + "_is_tag_in_global_scope", "_invalid_children", "_memoization_mode", "_rename_props", diff --git a/tests/units/components/base/test_bare.py b/tests/units/components/base/test_bare.py index 178820cffb7..6ae1e4db643 100644 --- a/tests/units/components/base/test_bare.py +++ b/tests/units/components/base/test_bare.py @@ -9,10 +9,10 @@ @pytest.mark.parametrize( "contents,expected", [ - ("hello", '{"hello"}'), - ("{}", '{"{}"}'), - (None, '{""}'), - (STATE_VAR, "{default_state.name}"), + ("hello", '"hello"'), + ("{}", '"{}"'), + (None, '""'), + (STATE_VAR, "default_state.name"), ], ) def test_fstrings(contents, expected): diff --git a/tests/units/components/base/test_link.py b/tests/units/components/base/test_link.py index 7713330c4f6..3c1260e1293 100644 --- a/tests/units/components/base/test_link.py +++ b/tests/units/components/base/test_link.py @@ -3,13 +3,11 @@ def test_raw_link(): raw_link = RawLink.create("https://example.com").render() - assert raw_link["name"] == "link" - assert raw_link["children"][0]["contents"] == '{"https://example.com"}' + assert raw_link["name"] == '"link"' + assert raw_link["children"][0]["contents"] == '"https://example.com"' def test_script_tag(): script_tag = ScriptTag.create("console.log('Hello, world!');").render() - assert script_tag["name"] == "script" - assert ( - script_tag["children"][0]["contents"] == "{\"console.log('Hello, world!');\"}" - ) + assert script_tag["name"] == '"script"' + assert script_tag["children"][0]["contents"] == "\"console.log('Hello, world!');\"" diff --git a/tests/units/components/base/test_script.py b/tests/units/components/base/test_script.py index e9c40188bab..2db63130746 100644 --- a/tests/units/components/base/test_script.py +++ b/tests/units/components/base/test_script.py @@ -14,7 +14,7 @@ def test_script_inline(): assert render_dict["name"] == "Script" assert not render_dict["contents"] assert len(render_dict["children"]) == 1 - assert render_dict["children"][0]["contents"] == '{"let x = 42"}' + assert render_dict["children"][0]["contents"] == '"let x = 42"' def test_script_src(): @@ -24,7 +24,7 @@ def test_script_src(): assert render_dict["name"] == "Script" assert not render_dict["contents"] assert not render_dict["children"] - assert 'src={"foo.js"}' in render_dict["props"] + assert 'src:"foo.js"' in render_dict["props"] def test_script_neither(): @@ -62,14 +62,14 @@ def test_script_event_handler(): ) render_dict = component.render() assert ( - f'onReady={{((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_ready", ({{ }}), ({{ }})))], args, ({{ }}))))}}' + f'onReady:((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_ready", ({{ }}), ({{ }})))], args, ({{ }}))))' in render_dict["props"] ) assert ( - f'onLoad={{((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_load", ({{ }}), ({{ }})))], args, ({{ }}))))}}' + f'onLoad:((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_load", ({{ }}), ({{ }})))], args, ({{ }}))))' in render_dict["props"] ) assert ( - f'onError={{((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_error", ({{ }}), ({{ }})))], args, ({{ }}))))}}' + f'onError:((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_error", ({{ }}), ({{ }})))], args, ({{ }}))))' in render_dict["props"] ) diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index 57bf2bf0819..fb999d9eca6 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -57,7 +57,7 @@ def test_validate_cond(cond_state: BaseState): [true_value_text] = true_value["children"] assert true_value_text["name"] == "RadixThemesText" - assert true_value_text["children"][0]["contents"] == '{"cond is True"}' + assert true_value_text["children"][0]["contents"] == '"cond is True"' # false value false_value = condition["false_value"] @@ -65,7 +65,7 @@ def test_validate_cond(cond_state: BaseState): [false_value_text] = false_value["children"] assert false_value_text["name"] == "RadixThemesText" - assert false_value_text["children"][0]["contents"] == '{"cond is False"}' + assert false_value_text["children"][0]["contents"] == '"cond is False"' @pytest.mark.parametrize( diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index d72fa9350e4..4e9e51c476c 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -282,7 +282,7 @@ def test_foreach_component_styles(): ) ) component._add_style_recursive({box: {"color": "red"}}) - assert 'css={({ ["color"] : "red" })}' in str(component) + assert 'css:({ ["color"] : "red" })' in str(component) def test_foreach_component_state(): diff --git a/tests/units/components/core/test_html.py b/tests/units/components/core/test_html.py index bebb2587d91..611753fca1d 100644 --- a/tests/units/components/core/test_html.py +++ b/tests/units/components/core/test_html.py @@ -19,7 +19,7 @@ def test_html_create(): assert str(html.dangerouslySetInnerHTML) == '({ ["__html"] : "

Hello !

" })' # pyright: ignore [reportAttributeAccessIssue] assert ( str(html) - == '
Hello !

" })}/>' + == 'jsx("div",{className:"rx-Html",dangerouslySetInnerHTML:({ ["__html"] : "

Hello !

" })},)\n' ) @@ -31,11 +31,13 @@ class TestState(State): html = Html.create(f"

Hello {TestState.myvar}!

") + html_dangerouslySetInnerHTML = html.dangerouslySetInnerHTML # pyright: ignore [reportAttributeAccessIssue] + assert ( - str(html.dangerouslySetInnerHTML) # pyright: ignore [reportAttributeAccessIssue] + str(html_dangerouslySetInnerHTML) == f'({{ ["__html"] : ("

Hello "+{TestState.myvar!s}+"!

") }})' ) assert ( str(html) - == f'
' # pyright: ignore [reportAttributeAccessIssue] + == f'jsx("div",{{className:"rx-Html",dangerouslySetInnerHTML:{html_dangerouslySetInnerHTML!s}}},)\n' ) diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 502de558bc3..ce17f84f22b 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -1,3 +1,4 @@ +import re from collections.abc import Mapping, Sequence import pytest @@ -47,7 +48,7 @@ def test_match_components(): assert match_cases[0][0]._var_type is int first_return_value_render = match_cases[0][1] assert first_return_value_render["name"] == "RadixThemesText" - assert first_return_value_render["children"][0]["contents"] == '{"first value"}' + assert first_return_value_render["children"][0]["contents"] == '"first value"' assert match_cases[1][0]._js_expr == "2" assert match_cases[1][0]._var_type is int @@ -55,36 +56,36 @@ def test_match_components(): assert match_cases[1][1]._var_type is int second_return_value_render = match_cases[1][2] assert second_return_value_render["name"] == "RadixThemesText" - assert second_return_value_render["children"][0]["contents"] == '{"second value"}' + assert second_return_value_render["children"][0]["contents"] == '"second value"' assert match_cases[2][0]._js_expr == "[1, 2]" assert match_cases[2][0]._var_type == Sequence[int] third_return_value_render = match_cases[2][1] assert third_return_value_render["name"] == "RadixThemesText" - assert third_return_value_render["children"][0]["contents"] == '{"third value"}' + assert third_return_value_render["children"][0]["contents"] == '"third value"' assert match_cases[3][0]._js_expr == '"random"' assert match_cases[3][0]._var_type is str fourth_return_value_render = match_cases[3][1] assert fourth_return_value_render["name"] == "RadixThemesText" - assert fourth_return_value_render["children"][0]["contents"] == '{"fourth value"}' + assert fourth_return_value_render["children"][0]["contents"] == '"fourth value"' assert match_cases[4][0]._js_expr == '({ ["foo"] : "bar" })' assert match_cases[4][0]._var_type == Mapping[str, str] fifth_return_value_render = match_cases[4][1] assert fifth_return_value_render["name"] == "RadixThemesText" - assert fifth_return_value_render["children"][0]["contents"] == '{"fifth value"}' + assert fifth_return_value_render["children"][0]["contents"] == '"fifth value"' assert match_cases[5][0]._js_expr == f"({MatchState.get_name()}.num + 1)" assert match_cases[5][0]._var_type is int fifth_return_value_render = match_cases[5][1] assert fifth_return_value_render["name"] == "RadixThemesText" - assert fifth_return_value_render["children"][0]["contents"] == '{"sixth value"}' + assert fifth_return_value_render["children"][0]["contents"] == '"sixth value"' default = match_child["default"] assert default["name"] == "RadixThemesText" - assert default["children"][0]["contents"] == '{"default value"}' + assert default["children"][0]["contents"] == '"default value"' @pytest.mark.parametrize( @@ -264,7 +265,7 @@ def test_match_case_tuple_elements(match_case): ([1, 2], rx.text("third value")), rx.text("default value"), ), - 'Match cases should have the same return types. Case 3 with return value ` {"first value"} ` ' + 'Match cases should have the same return types. Case 3 with return value `jsx( RadixThemesText, {as:"p"}, "first value" ,)` ' "of type is not ", ), ], @@ -276,7 +277,7 @@ def test_match_different_return_types(cases: tuple, error_msg: str): cases: The match cases. error_msg: Expected error message. """ - with pytest.raises(MatchTypeError, match=error_msg): + with pytest.raises(MatchTypeError, match=re.escape(error_msg)): Match.create(MatchState.value, *cases) diff --git a/tests/units/components/datadisplay/test_dataeditor.py b/tests/units/components/datadisplay/test_dataeditor.py index 63b156e7488..6b0d3ac6103 100644 --- a/tests/units/components/datadisplay/test_dataeditor.py +++ b/tests/units/components/datadisplay/test_dataeditor.py @@ -4,8 +4,8 @@ def test_dataeditor(): editor_wrapper = DataEditor.create().render() editor = editor_wrapper["children"][0] - assert editor_wrapper["name"] == "div" + assert editor_wrapper["name"] == '"div"' assert editor_wrapper["props"] == [ - 'css={({ ["width"] : "100%", ["height"] : "100%" })}' + 'css:({ ["width"] : "100%", ["height"] : "100%" })' ] assert editor["name"] == "DataEditor" diff --git a/tests/units/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py index 2dece464afa..2b47669222d 100644 --- a/tests/units/components/datadisplay/test_datatable.py +++ b/tests/units/components/datadisplay/test_datatable.py @@ -47,8 +47,8 @@ def test_validate_data_table(data_table_state: rx.State, expected): expected = f"{state_name}.{expected}" if expected else state_name assert data_table_dict["props"] == [ - f"columns={{{expected}.columns}}", - f"data={{{expected}.data}}", + f"columns:{expected}.columns", + f"data:{expected}.data", ] diff --git a/tests/units/components/el/test_svg.py b/tests/units/components/el/test_svg.py index 29aaa96dd8f..e104c4e15b2 100644 --- a/tests/units/components/el/test_svg.py +++ b/tests/units/components/el/test_svg.py @@ -16,59 +16,59 @@ def test_circle(): circle = Circle.create().render() - assert circle["name"] == "circle" + assert circle["name"] == '"circle"' def test_defs(): defs = Defs.create().render() - assert defs["name"] == "defs" + assert defs["name"] == '"defs"' def test_ellipse(): ellipse = Ellipse.create().render() - assert ellipse["name"] == "ellipse" + assert ellipse["name"] == '"ellipse"' def test_line(): line = Line.create().render() - assert line["name"] == "line" + assert line["name"] == '"line"' def test_linear_gradient(): linear_gradient = LinearGradient.create().render() - assert linear_gradient["name"] == "linearGradient" + assert linear_gradient["name"] == '"linearGradient"' def test_path(): path = Path.create().render() - assert path["name"] == "path" + assert path["name"] == '"path"' def test_polygon(): polygon = Polygon.create().render() - assert polygon["name"] == "polygon" + assert polygon["name"] == '"polygon"' def test_radial_gradient(): radial_gradient = RadialGradient.create().render() - assert radial_gradient["name"] == "radialGradient" + assert radial_gradient["name"] == '"radialGradient"' def test_rect(): rect = Rect.create().render() - assert rect["name"] == "rect" + assert rect["name"] == '"rect"' def test_svg(): svg = Svg.create().render() - assert svg["name"] == "svg" + assert svg["name"] == '"svg"' def test_text(): text = Text.create().render() - assert text["name"] == "text" + assert text["name"] == '"text"' def test_stop(): stop = Stop.create().render() - assert stop["name"] == "stop" + assert stop["name"] == '"stop"' diff --git a/tests/units/components/forms/test_form.py b/tests/units/components/forms/test_form.py index 4c05b4fe14b..6b3d4a06001 100644 --- a/tests/units/components/forms/test_form.py +++ b/tests/units/components/forms/test_form.py @@ -11,7 +11,7 @@ def test_render_on_submit(): ) f = Form.create(on_submit=submit_it) exp_submit_name = f"handleSubmit_{f.handle_submit_unique_name}" # pyright: ignore [reportAttributeAccessIssue] - assert f"onSubmit={{{exp_submit_name}}}" in f.render()["props"] + assert f"onSubmit:{exp_submit_name}" in f.render()["props"] def test_render_no_on_submit(): diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index 19fc32baf77..ce3c8f5b3a1 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -146,7 +146,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe ( "code", {}, - r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; if (_language) { if (!["abap", "abnf", "actionscript", "ada", "agda", "al", "antlr4", "apacheconf", "apex", "apl", "applescript", "aql", "arduino", "arff", "asciidoc", "asm6502", "asmatmel", "aspnet", "autohotkey", "autoit", "avisynth", "avro-idl", "bash", "basic", "batch", "bbcode", "bicep", "birb", "bison", "bnf", "brainfuck", "brightscript", "bro", "bsl", "c", "cfscript", "chaiscript", "cil", "clike", "clojure", "cmake", "cobol", "coffeescript", "concurnas", "coq", "core", "cpp", "crystal", "csharp", "cshtml", "csp", "css", "css-extras", "csv", "cypher", "d", "dart", "dataweave", "dax", "dhall", "diff", "django", "dns-zone-file", "docker", "dot", "ebnf", "editorconfig", "eiffel", "ejs", "elixir", "elm", "erb", "erlang", "etlua", "excel-formula", "factor", "false", "firestore-security-rules", "flow", "fortran", "fsharp", "ftl", "gap", "gcode", "gdscript", "gedcom", "gherkin", "git", "glsl", "gml", "gn", "go", "go-module", "graphql", "groovy", "haml", "handlebars", "haskell", "haxe", "hcl", "hlsl", "hoon", "hpkp", "hsts", "http", "ichigojam", "icon", "icu-message-format", "idris", "iecst", "ignore", "index", "inform7", "ini", "io", "j", "java", "javadoc", "javadoclike", "javascript", "javastacktrace", "jexl", "jolie", "jq", "js-extras", "js-templates", "jsdoc", "json", "json5", "jsonp", "jsstacktrace", "jsx", "julia", "keepalived", "keyman", "kotlin", "kumir", "kusto", "latex", "latte", "less", "lilypond", "liquid", "lisp", "livescript", "llvm", "log", "lolcode", "lua", "magma", "makefile", "markdown", "markup", "markup-templating", "matlab", "maxscript", "mel", "mermaid", "mizar", "mongodb", "monkey", "moonscript", "n1ql", "n4js", "nand2tetris-hdl", "naniscript", "nasm", "neon", "nevod", "nginx", "nim", "nix", "nsis", "objectivec", "ocaml", "opencl", "openqasm", "oz", "parigp", "parser", "pascal", "pascaligo", "pcaxis", "peoplecode", "perl", "php", "php-extras", "phpdoc", "plsql", "powerquery", "powershell", "processing", "prolog", "promql", "properties", "protobuf", "psl", "pug", "puppet", "pure", "purebasic", "purescript", "python", "q", "qml", "qore", "qsharp", "r", "racket", "reason", "regex", "rego", "renpy", "rest", "rip", "roboconf", "robotframework", "ruby", "rust", "sas", "sass", "scala", "scheme", "scss", "shell-session", "smali", "smalltalk", "smarty", "sml", "solidity", "solution-file", "soy", "sparql", "splunk-spl", "sqf", "sql", "squirrel", "stan", "stylus", "swift", "systemd", "t4-cs", "t4-templating", "t4-vb", "tap", "tcl", "textile", "toml", "tremor", "tsx", "tt2", "turtle", "twig", "typescript", "typoscript", "unrealscript", "uorazor", "uri", "v", "vala", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "warpscript", "wasm", "web-idl", "wiki", "wolfram", "wren", "xeora", "xml-doc", "xojo", "xquery", "yaml", "yang", "zig"].includes(_language)) { console.warn(`Language \`${_language}\` is not supported for code blocks inside of markdown.`); _language = ''; } else { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Language ${_language} is not supported for code blocks inside of markdown: `, error); } })(); } } ; return inline ? ( {children} ) : ( ); })""", + r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; if (_language) { if (!["abap", "abnf", "actionscript", "ada", "agda", "al", "antlr4", "apacheconf", "apex", "apl", "applescript", "aql", "arduino", "arff", "asciidoc", "asm6502", "asmatmel", "aspnet", "autohotkey", "autoit", "avisynth", "avro-idl", "bash", "basic", "batch", "bbcode", "bicep", "birb", "bison", "bnf", "brainfuck", "brightscript", "bro", "bsl", "c", "cfscript", "chaiscript", "cil", "clike", "clojure", "cmake", "cobol", "coffeescript", "concurnas", "coq", "core", "cpp", "crystal", "csharp", "cshtml", "csp", "css", "css-extras", "csv", "cypher", "d", "dart", "dataweave", "dax", "dhall", "diff", "django", "dns-zone-file", "docker", "dot", "ebnf", "editorconfig", "eiffel", "ejs", "elixir", "elm", "erb", "erlang", "etlua", "excel-formula", "factor", "false", "firestore-security-rules", "flow", "fortran", "fsharp", "ftl", "gap", "gcode", "gdscript", "gedcom", "gherkin", "git", "glsl", "gml", "gn", "go", "go-module", "graphql", "groovy", "haml", "handlebars", "haskell", "haxe", "hcl", "hlsl", "hoon", "hpkp", "hsts", "http", "ichigojam", "icon", "icu-message-format", "idris", "iecst", "ignore", "index", "inform7", "ini", "io", "j", "java", "javadoc", "javadoclike", "javascript", "javastacktrace", "jexl", "jolie", "jq", "js-extras", "js-templates", "jsdoc", "json", "json5", "jsonp", "jsstacktrace", "jsx", "julia", "keepalived", "keyman", "kotlin", "kumir", "kusto", "latex", "latte", "less", "lilypond", "liquid", "lisp", "livescript", "llvm", "log", "lolcode", "lua", "magma", "makefile", "markdown", "markup", "markup-templating", "matlab", "maxscript", "mel", "mermaid", "mizar", "mongodb", "monkey", "moonscript", "n1ql", "n4js", "nand2tetris-hdl", "naniscript", "nasm", "neon", "nevod", "nginx", "nim", "nix", "nsis", "objectivec", "ocaml", "opencl", "openqasm", "oz", "parigp", "parser", "pascal", "pascaligo", "pcaxis", "peoplecode", "perl", "php", "php-extras", "phpdoc", "plsql", "powerquery", "powershell", "processing", "prolog", "promql", "properties", "protobuf", "psl", "pug", "puppet", "pure", "purebasic", "purescript", "python", "q", "qml", "qore", "qsharp", "r", "racket", "reason", "regex", "rego", "renpy", "rest", "rip", "roboconf", "robotframework", "ruby", "rust", "sas", "sass", "scala", "scheme", "scss", "shell-session", "smali", "smalltalk", "smarty", "sml", "solidity", "solution-file", "soy", "sparql", "splunk-spl", "sqf", "sql", "squirrel", "stan", "stylus", "swift", "systemd", "t4-cs", "t4-templating", "t4-vb", "tap", "tcl", "textile", "toml", "tremor", "tsx", "tt2", "turtle", "twig", "typescript", "typoscript", "unrealscript", "uorazor", "uri", "v", "vala", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "warpscript", "wasm", "web-idl", "wiki", "wolfram", "wren", "xeora", "xml-doc", "xojo", "xquery", "yaml", "yang", "zig"].includes(_language)) { console.warn(`Language \`${_language}\` is not supported for code blocks inside of markdown.`); _language = ''; } else { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Language ${_language} is not supported for code blocks inside of markdown: `, error); } })(); } } ; return inline ? ( jsx(RadixThemesCode,{...props},children,) ) : ( jsx(SyntaxHighlighter,{children:((Array.isArray(children)) ? children.join("\n") : children),css:({ ["marginTop"] : "1em", ["marginBottom"] : "1em" }),customStyle:({ ["marginTop"] : "1em", ["marginBottom"] : "1em" }),language:_language,style:((resolvedColorMode === "light") ? oneLight : oneDark),wrapLongLines:true,...props},) ); })""", ), ( "code", @@ -155,7 +155,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe value, **props ) }, - r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; ; return inline ? ( {children} ) : ( ); })""", + r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; ; return inline ? ( jsx(RadixThemesCode,{...props},children,) ) : ( jsx(RadixThemesBox,{css:({ ["pre"] : ({ ["margin"] : "0", ["padding"] : "24px", ["background"] : "transparent", ["overflowX"] : "auto", ["borderRadius"] : "6px" }) }),...props},jsx(ShikiCode,{code:((Array.isArray(children)) ? children.join("\n") : children),decorations:[],language:_language,theme:((resolvedColorMode === "light") ? "one-light" : "one-dark-pro"),transformers:[]},),) ); })""", ), ( "h1", @@ -164,12 +164,12 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe Heading.create(value, as_="h1", size="6", margin_y="0.5em") ) }, - """(({custom_node, custom_children, custom_props}) => ({children}))""", + """(({custom_node, custom_children, custom_props}) => (jsx(CustomMarkdownComponent,{...props},jsx(RadixThemesHeading,{as:"h1",css:({ ["marginTop"] : "0.5em", ["marginBottom"] : "0.5em" }),size:"6"},children,),)))""", ), ( "code", {"codeblock": syntax_highlighter_memoized_component(CodeBlock)}, - r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; if (_language) { if (!["abap", "abnf", "actionscript", "ada", "agda", "al", "antlr4", "apacheconf", "apex", "apl", "applescript", "aql", "arduino", "arff", "asciidoc", "asm6502", "asmatmel", "aspnet", "autohotkey", "autoit", "avisynth", "avro-idl", "bash", "basic", "batch", "bbcode", "bicep", "birb", "bison", "bnf", "brainfuck", "brightscript", "bro", "bsl", "c", "cfscript", "chaiscript", "cil", "clike", "clojure", "cmake", "cobol", "coffeescript", "concurnas", "coq", "core", "cpp", "crystal", "csharp", "cshtml", "csp", "css", "css-extras", "csv", "cypher", "d", "dart", "dataweave", "dax", "dhall", "diff", "django", "dns-zone-file", "docker", "dot", "ebnf", "editorconfig", "eiffel", "ejs", "elixir", "elm", "erb", "erlang", "etlua", "excel-formula", "factor", "false", "firestore-security-rules", "flow", "fortran", "fsharp", "ftl", "gap", "gcode", "gdscript", "gedcom", "gherkin", "git", "glsl", "gml", "gn", "go", "go-module", "graphql", "groovy", "haml", "handlebars", "haskell", "haxe", "hcl", "hlsl", "hoon", "hpkp", "hsts", "http", "ichigojam", "icon", "icu-message-format", "idris", "iecst", "ignore", "index", "inform7", "ini", "io", "j", "java", "javadoc", "javadoclike", "javascript", "javastacktrace", "jexl", "jolie", "jq", "js-extras", "js-templates", "jsdoc", "json", "json5", "jsonp", "jsstacktrace", "jsx", "julia", "keepalived", "keyman", "kotlin", "kumir", "kusto", "latex", "latte", "less", "lilypond", "liquid", "lisp", "livescript", "llvm", "log", "lolcode", "lua", "magma", "makefile", "markdown", "markup", "markup-templating", "matlab", "maxscript", "mel", "mermaid", "mizar", "mongodb", "monkey", "moonscript", "n1ql", "n4js", "nand2tetris-hdl", "naniscript", "nasm", "neon", "nevod", "nginx", "nim", "nix", "nsis", "objectivec", "ocaml", "opencl", "openqasm", "oz", "parigp", "parser", "pascal", "pascaligo", "pcaxis", "peoplecode", "perl", "php", "php-extras", "phpdoc", "plsql", "powerquery", "powershell", "processing", "prolog", "promql", "properties", "protobuf", "psl", "pug", "puppet", "pure", "purebasic", "purescript", "python", "q", "qml", "qore", "qsharp", "r", "racket", "reason", "regex", "rego", "renpy", "rest", "rip", "roboconf", "robotframework", "ruby", "rust", "sas", "sass", "scala", "scheme", "scss", "shell-session", "smali", "smalltalk", "smarty", "sml", "solidity", "solution-file", "soy", "sparql", "splunk-spl", "sqf", "sql", "squirrel", "stan", "stylus", "swift", "systemd", "t4-cs", "t4-templating", "t4-vb", "tap", "tcl", "textile", "toml", "tremor", "tsx", "tt2", "turtle", "twig", "typescript", "typoscript", "unrealscript", "uorazor", "uri", "v", "vala", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "warpscript", "wasm", "web-idl", "wiki", "wolfram", "wren", "xeora", "xml-doc", "xojo", "xquery", "yaml", "yang", "zig"].includes(_language)) { console.warn(`Language \`${_language}\` is not supported for code blocks inside of markdown.`); _language = ''; } else { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Language ${_language} is not supported for code blocks inside of markdown: `, error); } })(); } } ; return inline ? ( {children} ) : ( ); })""", + r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; if (_language) { if (!["abap", "abnf", "actionscript", "ada", "agda", "al", "antlr4", "apacheconf", "apex", "apl", "applescript", "aql", "arduino", "arff", "asciidoc", "asm6502", "asmatmel", "aspnet", "autohotkey", "autoit", "avisynth", "avro-idl", "bash", "basic", "batch", "bbcode", "bicep", "birb", "bison", "bnf", "brainfuck", "brightscript", "bro", "bsl", "c", "cfscript", "chaiscript", "cil", "clike", "clojure", "cmake", "cobol", "coffeescript", "concurnas", "coq", "core", "cpp", "crystal", "csharp", "cshtml", "csp", "css", "css-extras", "csv", "cypher", "d", "dart", "dataweave", "dax", "dhall", "diff", "django", "dns-zone-file", "docker", "dot", "ebnf", "editorconfig", "eiffel", "ejs", "elixir", "elm", "erb", "erlang", "etlua", "excel-formula", "factor", "false", "firestore-security-rules", "flow", "fortran", "fsharp", "ftl", "gap", "gcode", "gdscript", "gedcom", "gherkin", "git", "glsl", "gml", "gn", "go", "go-module", "graphql", "groovy", "haml", "handlebars", "haskell", "haxe", "hcl", "hlsl", "hoon", "hpkp", "hsts", "http", "ichigojam", "icon", "icu-message-format", "idris", "iecst", "ignore", "index", "inform7", "ini", "io", "j", "java", "javadoc", "javadoclike", "javascript", "javastacktrace", "jexl", "jolie", "jq", "js-extras", "js-templates", "jsdoc", "json", "json5", "jsonp", "jsstacktrace", "jsx", "julia", "keepalived", "keyman", "kotlin", "kumir", "kusto", "latex", "latte", "less", "lilypond", "liquid", "lisp", "livescript", "llvm", "log", "lolcode", "lua", "magma", "makefile", "markdown", "markup", "markup-templating", "matlab", "maxscript", "mel", "mermaid", "mizar", "mongodb", "monkey", "moonscript", "n1ql", "n4js", "nand2tetris-hdl", "naniscript", "nasm", "neon", "nevod", "nginx", "nim", "nix", "nsis", "objectivec", "ocaml", "opencl", "openqasm", "oz", "parigp", "parser", "pascal", "pascaligo", "pcaxis", "peoplecode", "perl", "php", "php-extras", "phpdoc", "plsql", "powerquery", "powershell", "processing", "prolog", "promql", "properties", "protobuf", "psl", "pug", "puppet", "pure", "purebasic", "purescript", "python", "q", "qml", "qore", "qsharp", "r", "racket", "reason", "regex", "rego", "renpy", "rest", "rip", "roboconf", "robotframework", "ruby", "rust", "sas", "sass", "scala", "scheme", "scss", "shell-session", "smali", "smalltalk", "smarty", "sml", "solidity", "solution-file", "soy", "sparql", "splunk-spl", "sqf", "sql", "squirrel", "stan", "stylus", "swift", "systemd", "t4-cs", "t4-templating", "t4-vb", "tap", "tcl", "textile", "toml", "tremor", "tsx", "tt2", "turtle", "twig", "typescript", "typoscript", "unrealscript", "uorazor", "uri", "v", "vala", "vbnet", "velocity", "verilog", "vhdl", "vim", "visual-basic", "warpscript", "wasm", "web-idl", "wiki", "wolfram", "wren", "xeora", "xml-doc", "xojo", "xquery", "yaml", "yang", "zig"].includes(_language)) { console.warn(`Language \`${_language}\` is not supported for code blocks inside of markdown.`); _language = ''; } else { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Language ${_language} is not supported for code blocks inside of markdown: `, error); } })(); } } ; return inline ? ( jsx(RadixThemesCode,{...props},children,) ) : ( jsx(CodeBlock,{code:((Array.isArray(children)) ? children.join("\n") : children),language:_language,...props},) ); })""", ), ( "code", @@ -178,7 +178,7 @@ def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expe ShikiHighLevelCodeBlock ) }, - r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; ; return inline ? ( {children} ) : ( ); })""", + r"""(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?.*)/); let _language = match ? match[1] : ''; ; return inline ? ( jsx(RadixThemesCode,{...props},children,) ) : ( jsx(CodeBlock,{code:((Array.isArray(children)) ? children.join("\n") : children),language:_language,...props},) ); })""", ), ], ) diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index d6eb04848da..b8d1e68af34 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -686,14 +686,14 @@ def test_component_create_unallowed_types(children, test_component): "children": [ { "name": "RadixThemesText", - "props": ['as={"p"}'], + "props": ['as:"p"'], "contents": "", "special_props": [], "children": [ { "name": "", "props": [], - "contents": '{"first_text"}', + "contents": '"first_text"', "special_props": [], "children": [], "autofocus": False, @@ -716,7 +716,7 @@ def test_component_create_unallowed_types(children, test_component): { "autofocus": False, "children": [], - "contents": '{"first_text"}', + "contents": '"first_text"', "name": "", "props": [], "special_props": [], @@ -724,7 +724,7 @@ def test_component_create_unallowed_types(children, test_component): ], "contents": "", "name": "RadixThemesText", - "props": ['as={"p"}'], + "props": ['as:"p"'], "special_props": [], }, { @@ -733,7 +733,7 @@ def test_component_create_unallowed_types(children, test_component): { "autofocus": False, "children": [], - "contents": '{"second_text"}', + "contents": '"second_text"', "name": "", "props": [], "special_props": [], @@ -741,7 +741,7 @@ def test_component_create_unallowed_types(children, test_component): ], "contents": "", "name": "RadixThemesText", - "props": ['as={"p"}'], + "props": ['as:"p"'], "special_props": [], }, ], @@ -762,7 +762,7 @@ def test_component_create_unallowed_types(children, test_component): { "autofocus": False, "children": [], - "contents": '{"first_text"}', + "contents": '"first_text"', "name": "", "props": [], "special_props": [], @@ -770,7 +770,7 @@ def test_component_create_unallowed_types(children, test_component): ], "contents": "", "name": "RadixThemesText", - "props": ['as={"p"}'], + "props": ['as:"p"'], "special_props": [], }, { @@ -785,7 +785,7 @@ def test_component_create_unallowed_types(children, test_component): { "autofocus": False, "children": [], - "contents": '{"second_text"}', + "contents": '"second_text"', "name": "", "props": [], "special_props": [], @@ -793,7 +793,7 @@ def test_component_create_unallowed_types(children, test_component): ], "contents": "", "name": "RadixThemesText", - "props": ['as={"p"}'], + "props": ['as:"p"'], "special_props": [], } ], @@ -1163,10 +1163,10 @@ def test_component_with_only_valid_children(fixture, request): @pytest.mark.parametrize( "component,rendered", [ - (rx.text("hi"), '\n\n{"hi"}\n'), + (rx.text("hi"), 'jsx(\nRadixThemesText,\n{as:"p"},\n"hi"\n,)'), ( rx.box(rx.heading("test", size="3")), - '\n\n\n\n{"test"}\n\n', + 'jsx(\nRadixThemesBox,\n{},\njsx(\nRadixThemesHeading,\n{size:"3"},\n"test"\n,),)', ), ], ) @@ -1771,14 +1771,14 @@ class C2(C1): c1 = C1.create(prop1="prop1_1", prop2="prop2_1") rendered_c1 = c1.render() - assert 'renamed_prop1={"prop1_1"}' in rendered_c1["props"] - assert 'renamed_prop2={"prop2_1"}' in rendered_c1["props"] + assert 'renamed_prop1:"prop1_1"' in rendered_c1["props"] + assert 'renamed_prop2:"prop2_1"' in rendered_c1["props"] c2 = C2.create(prop1="prop1_2", prop2="prop2_2", prop3="prop3_2") rendered_c2 = c2.render() - assert 'renamed_prop1={"prop1_2"}' in rendered_c2["props"] - assert 'subclass_prop2={"prop2_2"}' in rendered_c2["props"] - assert 'renamed_prop3={"prop3_2"}' in rendered_c2["props"] + assert 'renamed_prop1:"prop1_2"' in rendered_c2["props"] + assert 'subclass_prop2:"prop2_2"' in rendered_c2["props"] + assert 'renamed_prop3:"prop3_2"' in rendered_c2["props"] def test_custom_component_get_imports(): @@ -2165,7 +2165,7 @@ def add_style(self): # pyright: ignore [reportIncompatibleMethodOverride] assert "useParent" in page._get_all_hooks_internal() assert ( str(page).count( - f'css={{({{ ["fakeParent"] : "parent", ["color"] : "var(--plum-10)", ["fake"] : "text", ["margin"] : ({test_state.get_name()}.num+"%") }})}}' + f'css:({{ ["fakeParent"] : "parent", ["color"] : "var(--plum-10)", ["fake"] : "text", ["margin"] : ({test_state.get_name()}.num+"%") }})' ) == 1 ) @@ -2186,10 +2186,10 @@ def add_style(self): assert len(page.children[0].children) == 1 # Expect the style to be added to the child of the foreach - assert 'css={({ ["color"] : "red" })}' in str(page.children[0].children[0]) + assert 'css:({ ["color"] : "red" })' in str(page.children[0].children[0]) # Expect only one instance of this CSS dict in the rendered page - assert str(page).count('css={({ ["color"] : "red" })}') == 1 + assert str(page).count('css:({ ["color"] : "red" })') == 1 class TriggerState(rx.State): diff --git a/tests/units/components/test_tag.py b/tests/units/components/test_tag.py index c31f9157ee4..b3ff2b8edc7 100644 --- a/tests/units/components/test_tag.py +++ b/tests/units/components/test_tag.py @@ -8,10 +8,10 @@ "props,test_props", [ ({}, []), - ({"key-hypen": 1}, ["key-hypen={1}"]), - ({"key": 1}, ["key={1}"]), - ({"key": "value"}, ['key={"value"}']), - ({"key": True, "key2": "value2"}, ["key={true}", 'key2={"value2"}']), + ({"key-hypen": 1}, ['"key-hypen":1']), + ({"key": 1}, ["key:1"]), + ({"key": "value"}, ['key:"value"']), + ({"key": True, "key2": "value2"}, ["key:true", 'key2:"value2"']), ], ) def test_format_props(props: dict[str, Var], test_props: list): diff --git a/tests/units/test_app.py b/tests/units/test_app.py index d42772622ef..98bba42516b 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -1364,24 +1364,23 @@ def test_app_wrap_compile_theme( line.strip() for line in app_js_contents.splitlines() if line.strip() ] lines = "".join(app_js_lines) - assert ( + expected = ( "function AppWrap({children}) {" "return (" - + ("" if react_strict_mode else "") - + "" - "" - "" - "" - "" - "{children}" - "" - "" - "" - "" - + ("" if react_strict_mode else "") - + ")" + + ("jsx(StrictMode,{}," if react_strict_mode else "") + + "jsx(RadixThemesColorModeProvider,{}," + "jsx(RadixThemesTheme,{accentColor:\"plum\",css:{...theme.styles.global[':root'], ...theme.styles.global.body}}," + "jsx(Fragment,{}," + "jsx(MemoizedToastProvider,{},)," + "jsx(Fragment,{}," + "children," + ")," + ")," + ")," + ")" + (",)" if react_strict_mode else "") + ")" "}" - ) in lines + ) + assert expected in lines @pytest.mark.parametrize( @@ -1431,23 +1430,21 @@ def page(): line.strip() for line in app_js_contents.splitlines() if line.strip() ] lines = "".join(app_js_lines) - assert ( + expected = ( "function AppWrap({children}) {" - "return (" + ("" if react_strict_mode else "") + "" - '' - "" - "" - "" - "" - "" - "{children}" - "" - "" - "" - "" - "" - "" + ("" if react_strict_mode else "") - ) in lines + "return (" + + ("jsx(StrictMode,{}," if react_strict_mode else "") + + "jsx(RadixThemesBox,{}," + 'jsx(RadixThemesText,{as:"p"},' + "jsx(RadixThemesColorModeProvider,{}," + "jsx(Fragment2,{}," + "jsx(Fragment,{}," + "jsx(MemoizedToastProvider,{},)," + "jsx(Fragment,{}," + "children" + ",),),),),)" + (",)" if react_strict_mode else "") + ) + assert expected in lines def test_app_state_determination(): diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index 776613a21cb..e2387c60d87 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -457,7 +457,7 @@ def test_format_match( _js_expr=f"(({{node, ...props}}) => )" ), }, - '({ ["h1"] : (({node, ...props}) => ) })', + '({ ["h1"] : (({node, ...props}) => ) })', ), ], ) @@ -475,9 +475,9 @@ def test_format_prop(prop: Var, formatted: str): "single_props,key_value_props,output", [ ( - [Var(_js_expr="{...props}")], + [Var(_js_expr="props")], {"key": 42}, - ["key={42}", "{...props}"], + ["key:42", "...props"], ), ], )