components as literal vars (#4223)
* component as literal vars * fix pyi * use render * fix pyi * only render once * add type ignore * fix upload default value * remove testcases if you don't pass them * improve behavior * fix render * that's not how icon buttons work * upgrade to next js 15 and remove babel and enable turbo * upload is a silly guy * woops * how did this work before * set env variable * lower it even more * lower it even more * lower it even more * only do literals as component vars
This commit is contained in:
parent
c8a7ee52bf
commit
24363170d3
@ -36,14 +36,10 @@
|
|||||||
{# component: component dictionary #}
|
{# component: component dictionary #}
|
||||||
{% macro render_tag(component) %}
|
{% macro render_tag(component) %}
|
||||||
<{{component.name}} {{- render_props(component.props) }}>
|
<{{component.name}} {{- render_props(component.props) }}>
|
||||||
{%- if component.args is not none -%}
|
{{ component.contents }}
|
||||||
{{- render_arg_content(component) }}
|
{% for child in component.children %}
|
||||||
{%- else -%}
|
{{ render(child) }}
|
||||||
{{ component.contents }}
|
{% endfor %}
|
||||||
{% for child in component.children %}
|
|
||||||
{{ render(child) }}
|
|
||||||
{% endfor %}
|
|
||||||
{%- endif -%}
|
|
||||||
</{{component.name}}>
|
</{{component.name}}>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ import {
|
|||||||
} from "$/utils/context.js";
|
} from "$/utils/context.js";
|
||||||
import debounce from "$/utils/helpers/debounce";
|
import debounce from "$/utils/helpers/debounce";
|
||||||
import throttle from "$/utils/helpers/throttle";
|
import throttle from "$/utils/helpers/throttle";
|
||||||
import * as Babel from "@babel/standalone";
|
|
||||||
|
|
||||||
// Endpoint URLs.
|
// Endpoint URLs.
|
||||||
const EVENTURL = env.EVENT;
|
const EVENTURL = env.EVENT;
|
||||||
@ -139,8 +138,7 @@ export const evalReactComponent = async (component) => {
|
|||||||
if (!window.React && window.__reflex) {
|
if (!window.React && window.__reflex) {
|
||||||
window.React = window.__reflex.react;
|
window.React = window.__reflex.react;
|
||||||
}
|
}
|
||||||
const output = Babel.transform(component, { presets: ["react"] }).code;
|
const encodedJs = encodeURIComponent(component);
|
||||||
const encodedJs = encodeURIComponent(output);
|
|
||||||
const dataUri = "data:text/javascript;charset=utf-8," + encodedJs;
|
const dataUri = "data:text/javascript;charset=utf-8," + encodedJs;
|
||||||
const module = await eval(`import(dataUri)`);
|
const module = await eval(`import(dataUri)`);
|
||||||
return module.default;
|
return module.default;
|
||||||
|
@ -4,10 +4,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any, Iterator
|
from typing import Any, Iterator
|
||||||
|
|
||||||
from reflex.components.component import Component
|
from reflex.components.component import Component, LiteralComponentVar
|
||||||
from reflex.components.tags import Tag
|
from reflex.components.tags import Tag
|
||||||
from reflex.components.tags.tagless import Tagless
|
from reflex.components.tags.tagless import Tagless
|
||||||
from reflex.vars import ArrayVar, BooleanVar, ObjectVar, Var
|
from reflex.utils.imports import ParsedImportDict
|
||||||
|
from reflex.vars import BooleanVar, ObjectVar, Var
|
||||||
|
|
||||||
|
|
||||||
class Bare(Component):
|
class Bare(Component):
|
||||||
@ -31,9 +32,77 @@ class Bare(Component):
|
|||||||
contents = str(contents) if contents is not None else ""
|
contents = str(contents) if contents is not None else ""
|
||||||
return cls(contents=contents) # type: ignore
|
return cls(contents=contents) # type: ignore
|
||||||
|
|
||||||
|
def _get_all_hooks_internal(self) -> dict[str, None]:
|
||||||
|
"""Include the hooks for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The hooks for the component.
|
||||||
|
"""
|
||||||
|
hooks = super()._get_all_hooks_internal()
|
||||||
|
if isinstance(self.contents, LiteralComponentVar):
|
||||||
|
hooks |= self.contents._var_value._get_all_hooks_internal()
|
||||||
|
return hooks
|
||||||
|
|
||||||
|
def _get_all_hooks(self) -> dict[str, None]:
|
||||||
|
"""Include the hooks for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The hooks for the component.
|
||||||
|
"""
|
||||||
|
hooks = super()._get_all_hooks()
|
||||||
|
if isinstance(self.contents, LiteralComponentVar):
|
||||||
|
hooks |= self.contents._var_value._get_all_hooks()
|
||||||
|
return hooks
|
||||||
|
|
||||||
|
def _get_all_imports(self) -> ParsedImportDict:
|
||||||
|
"""Include the imports for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The imports for the component.
|
||||||
|
"""
|
||||||
|
imports = super()._get_all_imports()
|
||||||
|
if isinstance(self.contents, LiteralComponentVar):
|
||||||
|
var_data = self.contents._get_all_var_data()
|
||||||
|
if var_data:
|
||||||
|
imports |= {k: list(v) for k, v in var_data.imports}
|
||||||
|
return imports
|
||||||
|
|
||||||
|
def _get_all_dynamic_imports(self) -> set[str]:
|
||||||
|
"""Get dynamic imports for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The dynamic imports.
|
||||||
|
"""
|
||||||
|
dynamic_imports = super()._get_all_dynamic_imports()
|
||||||
|
if isinstance(self.contents, LiteralComponentVar):
|
||||||
|
dynamic_imports |= self.contents._var_value._get_all_dynamic_imports()
|
||||||
|
return dynamic_imports
|
||||||
|
|
||||||
|
def _get_all_custom_code(self) -> set[str]:
|
||||||
|
"""Get custom code for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The custom code.
|
||||||
|
"""
|
||||||
|
custom_code = super()._get_all_custom_code()
|
||||||
|
if isinstance(self.contents, LiteralComponentVar):
|
||||||
|
custom_code |= self.contents._var_value._get_all_custom_code()
|
||||||
|
return custom_code
|
||||||
|
|
||||||
|
def _get_all_refs(self) -> set[str]:
|
||||||
|
"""Get the refs for the children of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The refs for the children.
|
||||||
|
"""
|
||||||
|
refs = super()._get_all_refs()
|
||||||
|
if isinstance(self.contents, LiteralComponentVar):
|
||||||
|
refs |= self.contents._var_value._get_all_refs()
|
||||||
|
return refs
|
||||||
|
|
||||||
def _render(self) -> Tag:
|
def _render(self) -> Tag:
|
||||||
if isinstance(self.contents, Var):
|
if isinstance(self.contents, Var):
|
||||||
if isinstance(self.contents, (BooleanVar, ObjectVar, ArrayVar)):
|
if isinstance(self.contents, (BooleanVar, ObjectVar)):
|
||||||
return Tagless(contents=f"{{{str(self.contents.to_string())}}}")
|
return Tagless(contents=f"{{{str(self.contents.to_string())}}}")
|
||||||
return Tagless(contents=f"{{{str(self.contents)}}}")
|
return Tagless(contents=f"{{{str(self.contents)}}}")
|
||||||
return Tagless(contents=str(self.contents))
|
return Tagless(contents=str(self.contents))
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import dataclasses
|
||||||
import typing
|
import typing
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from functools import lru_cache, wraps
|
from functools import lru_cache, wraps
|
||||||
@ -59,7 +60,15 @@ from reflex.utils.imports import (
|
|||||||
parse_imports,
|
parse_imports,
|
||||||
)
|
)
|
||||||
from reflex.vars import VarData
|
from reflex.vars import VarData
|
||||||
from reflex.vars.base import LiteralVar, Var
|
from reflex.vars.base import (
|
||||||
|
CachedVarOperation,
|
||||||
|
LiteralVar,
|
||||||
|
Var,
|
||||||
|
cached_property_no_lock,
|
||||||
|
)
|
||||||
|
from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar
|
||||||
|
from reflex.vars.number import ternary_operation
|
||||||
|
from reflex.vars.object import ObjectVar
|
||||||
from reflex.vars.sequence import LiteralArrayVar
|
from reflex.vars.sequence import LiteralArrayVar
|
||||||
|
|
||||||
|
|
||||||
@ -2345,3 +2354,203 @@ class MemoizationLeaf(Component):
|
|||||||
|
|
||||||
|
|
||||||
load_dynamic_serializer()
|
load_dynamic_serializer()
|
||||||
|
|
||||||
|
|
||||||
|
class ComponentVar(Var[Component], python_types=BaseComponent):
|
||||||
|
"""A Var that represents a Component."""
|
||||||
|
|
||||||
|
|
||||||
|
def empty_component() -> Component:
|
||||||
|
"""Create an empty component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An empty component.
|
||||||
|
"""
|
||||||
|
from reflex.components.base.bare import Bare
|
||||||
|
|
||||||
|
return Bare.create("")
|
||||||
|
|
||||||
|
|
||||||
|
def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) -> Var:
|
||||||
|
"""Convert a render dict to a Var.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tag: The render dict.
|
||||||
|
imported_names: The names of the imported components.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The Var.
|
||||||
|
"""
|
||||||
|
if not isinstance(tag, dict):
|
||||||
|
if isinstance(tag, Component):
|
||||||
|
return render_dict_to_var(tag.render(), imported_names)
|
||||||
|
return Var.create(tag)
|
||||||
|
|
||||||
|
if "iterable" in tag:
|
||||||
|
function_return = Var.create(
|
||||||
|
[
|
||||||
|
render_dict_to_var(child.render(), imported_names)
|
||||||
|
for child in tag["children"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
func = ArgsFunctionOperation.create(
|
||||||
|
(tag["arg_var_name"], tag["index_var_name"]),
|
||||||
|
function_return,
|
||||||
|
)
|
||||||
|
|
||||||
|
return FunctionStringVar.create("Array.prototype.map.call").call(
|
||||||
|
tag["iterable"]
|
||||||
|
if not isinstance(tag["iterable"], ObjectVar)
|
||||||
|
else tag["iterable"].items(),
|
||||||
|
func,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tag["name"] == "match":
|
||||||
|
element = tag["cond"]
|
||||||
|
|
||||||
|
conditionals = tag["default"]
|
||||||
|
|
||||||
|
for case in tag["match_cases"][::-1]:
|
||||||
|
condition = case[0].to_string() == element.to_string()
|
||||||
|
for pattern in case[1:-1]:
|
||||||
|
condition = condition | (pattern.to_string() == element.to_string())
|
||||||
|
|
||||||
|
conditionals = ternary_operation(
|
||||||
|
condition,
|
||||||
|
case[-1],
|
||||||
|
conditionals,
|
||||||
|
)
|
||||||
|
|
||||||
|
return conditionals
|
||||||
|
|
||||||
|
if "cond" in tag:
|
||||||
|
return ternary_operation(
|
||||||
|
tag["cond"],
|
||||||
|
render_dict_to_var(tag["true_value"], imported_names),
|
||||||
|
render_dict_to_var(tag["false_value"], imported_names)
|
||||||
|
if tag["false_value"] is not None
|
||||||
|
else Var.create(None),
|
||||||
|
)
|
||||||
|
|
||||||
|
props = {}
|
||||||
|
|
||||||
|
special_props = []
|
||||||
|
|
||||||
|
for prop_str in tag["props"]:
|
||||||
|
if "=" not in prop_str:
|
||||||
|
special_props.append(Var(prop_str).to(ObjectVar))
|
||||||
|
continue
|
||||||
|
prop = prop_str.index("=")
|
||||||
|
key = prop_str[:prop]
|
||||||
|
value = prop_str[prop + 2 : -1]
|
||||||
|
props[key] = value
|
||||||
|
|
||||||
|
props = Var.create({Var.create(k): Var(v) for k, v in props.items()})
|
||||||
|
|
||||||
|
for prop in special_props:
|
||||||
|
props = props.merge(prop)
|
||||||
|
|
||||||
|
contents = tag["contents"][1:-1] if tag["contents"] else None
|
||||||
|
|
||||||
|
raw_tag_name = tag.get("name")
|
||||||
|
tag_name = Var(raw_tag_name or "Fragment")
|
||||||
|
|
||||||
|
tag_name = (
|
||||||
|
Var.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(
|
||||||
|
tag_name,
|
||||||
|
props,
|
||||||
|
*([Var(contents)] if contents is not None else []),
|
||||||
|
*[render_dict_to_var(child, imported_names) for child in tag["children"]],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(
|
||||||
|
eq=False,
|
||||||
|
frozen=True,
|
||||||
|
)
|
||||||
|
class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
|
||||||
|
"""A Var that represents a Component."""
|
||||||
|
|
||||||
|
_var_value: BaseComponent = dataclasses.field(default_factory=empty_component)
|
||||||
|
|
||||||
|
@cached_property_no_lock
|
||||||
|
def _cached_var_name(self) -> str:
|
||||||
|
"""Get the name of the var.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The name of the var.
|
||||||
|
"""
|
||||||
|
var_data = self._get_all_var_data()
|
||||||
|
if var_data is not None:
|
||||||
|
# flatten imports
|
||||||
|
imported_names = {j.alias or j.name for i in var_data.imports for j in i[1]}
|
||||||
|
else:
|
||||||
|
imported_names = set()
|
||||||
|
return str(render_dict_to_var(self._var_value.render(), imported_names))
|
||||||
|
|
||||||
|
@cached_property_no_lock
|
||||||
|
def _cached_get_all_var_data(self) -> VarData | None:
|
||||||
|
"""Get the VarData for the var.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The VarData for the var.
|
||||||
|
"""
|
||||||
|
return VarData.merge(
|
||||||
|
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:
|
||||||
|
"""Get the hash of the var.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The hash of the var.
|
||||||
|
"""
|
||||||
|
return hash((self.__class__.__name__, self._js_expr))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(
|
||||||
|
cls,
|
||||||
|
value: Component,
|
||||||
|
_var_data: VarData | None = None,
|
||||||
|
):
|
||||||
|
"""Create a var from a value.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: The value of the var.
|
||||||
|
_var_data: Additional hooks and imports associated with the Var.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The var.
|
||||||
|
"""
|
||||||
|
return LiteralComponentVar(
|
||||||
|
_js_expr="",
|
||||||
|
_var_type=type(value),
|
||||||
|
_var_data=_var_data,
|
||||||
|
_var_value=value,
|
||||||
|
)
|
||||||
|
@ -5,11 +5,17 @@ from __future__ import annotations
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
|
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
|
from reflex.components.component import (
|
||||||
|
Component,
|
||||||
|
ComponentNamespace,
|
||||||
|
MemoizationLeaf,
|
||||||
|
StatefulComponent,
|
||||||
|
)
|
||||||
from reflex.components.el.elements.forms import Input
|
from reflex.components.el.elements.forms import Input
|
||||||
from reflex.components.radix.themes.layout.box import Box
|
from reflex.components.radix.themes.layout.box import Box
|
||||||
from reflex.config import environment
|
from reflex.config import environment
|
||||||
from reflex.constants import Dirs
|
from reflex.constants import Dirs
|
||||||
|
from reflex.constants.compiler import Imports
|
||||||
from reflex.event import (
|
from reflex.event import (
|
||||||
CallableEventSpec,
|
CallableEventSpec,
|
||||||
EventChain,
|
EventChain,
|
||||||
@ -19,9 +25,10 @@ from reflex.event import (
|
|||||||
call_script,
|
call_script,
|
||||||
parse_args_spec,
|
parse_args_spec,
|
||||||
)
|
)
|
||||||
|
from reflex.utils import format
|
||||||
from reflex.utils.imports import ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
from reflex.vars import VarData
|
from reflex.vars import VarData
|
||||||
from reflex.vars.base import CallableVar, LiteralVar, Var
|
from reflex.vars.base import CallableVar, LiteralVar, Var, get_unique_variable_name
|
||||||
from reflex.vars.sequence import LiteralStringVar
|
from reflex.vars.sequence import LiteralStringVar
|
||||||
|
|
||||||
DEFAULT_UPLOAD_ID: str = "default"
|
DEFAULT_UPLOAD_ID: str = "default"
|
||||||
@ -179,9 +186,7 @@ class Upload(MemoizationLeaf):
|
|||||||
|
|
||||||
library = "react-dropzone@14.2.10"
|
library = "react-dropzone@14.2.10"
|
||||||
|
|
||||||
tag = "ReactDropzone"
|
tag = ""
|
||||||
|
|
||||||
is_default = True
|
|
||||||
|
|
||||||
# The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as
|
# The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as
|
||||||
# values.
|
# values.
|
||||||
@ -201,7 +206,7 @@ class Upload(MemoizationLeaf):
|
|||||||
min_size: Var[int]
|
min_size: Var[int]
|
||||||
|
|
||||||
# Whether to allow multiple files to be uploaded.
|
# Whether to allow multiple files to be uploaded.
|
||||||
multiple: Var[bool] = True # type: ignore
|
multiple: Var[bool]
|
||||||
|
|
||||||
# Whether to disable click to upload.
|
# Whether to disable click to upload.
|
||||||
no_click: Var[bool]
|
no_click: Var[bool]
|
||||||
@ -232,6 +237,8 @@ class Upload(MemoizationLeaf):
|
|||||||
# Mark the Upload component as used in the app.
|
# Mark the Upload component as used in the app.
|
||||||
cls.is_used = True
|
cls.is_used = True
|
||||||
|
|
||||||
|
props.setdefault("multiple", True)
|
||||||
|
|
||||||
# Apply the default classname
|
# Apply the default classname
|
||||||
given_class_name = props.pop("class_name", [])
|
given_class_name = props.pop("class_name", [])
|
||||||
if isinstance(given_class_name, str):
|
if isinstance(given_class_name, str):
|
||||||
@ -243,17 +250,6 @@ class Upload(MemoizationLeaf):
|
|||||||
upload_props = {
|
upload_props = {
|
||||||
key: value for key, value in props.items() if key in supported_props
|
key: value for key, value in props.items() if key in supported_props
|
||||||
}
|
}
|
||||||
# The file input to use.
|
|
||||||
upload = Input.create(type="file")
|
|
||||||
upload.special_props = [Var(_js_expr="{...getInputProps()}", _var_type=None)]
|
|
||||||
|
|
||||||
# The dropzone to use.
|
|
||||||
zone = Box.create(
|
|
||||||
upload,
|
|
||||||
*children,
|
|
||||||
**{k: v for k, v in props.items() if k not in supported_props},
|
|
||||||
)
|
|
||||||
zone.special_props = [Var(_js_expr="{...getRootProps()}", _var_type=None)]
|
|
||||||
|
|
||||||
# Create the component.
|
# Create the component.
|
||||||
upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)
|
upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)
|
||||||
@ -275,9 +271,74 @@ class Upload(MemoizationLeaf):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
upload_props["on_drop"] = on_drop
|
upload_props["on_drop"] = on_drop
|
||||||
|
|
||||||
|
input_props_unique_name = get_unique_variable_name()
|
||||||
|
root_props_unique_name = get_unique_variable_name()
|
||||||
|
|
||||||
|
event_var, callback_str = StatefulComponent._get_memoized_event_triggers(
|
||||||
|
Box.create(on_click=upload_props["on_drop"]) # type: ignore
|
||||||
|
)["on_click"]
|
||||||
|
|
||||||
|
upload_props["on_drop"] = event_var
|
||||||
|
|
||||||
|
upload_props = {
|
||||||
|
format.to_camel_case(key): value for key, value in upload_props.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
use_dropzone_arguements = {
|
||||||
|
"onDrop": event_var,
|
||||||
|
**upload_props,
|
||||||
|
}
|
||||||
|
|
||||||
|
left_side = f"const {{getRootProps: {root_props_unique_name}, getInputProps: {input_props_unique_name}}} "
|
||||||
|
right_side = f"useDropzone({str(Var.create(use_dropzone_arguements))})"
|
||||||
|
|
||||||
|
var_data = VarData.merge(
|
||||||
|
VarData(
|
||||||
|
imports=Imports.EVENTS,
|
||||||
|
hooks={
|
||||||
|
"const [addEvents, connectError] = useContext(EventLoopContext);": None
|
||||||
|
},
|
||||||
|
),
|
||||||
|
event_var._get_all_var_data(),
|
||||||
|
VarData(
|
||||||
|
hooks={
|
||||||
|
callback_str: None,
|
||||||
|
f"{left_side} = {right_side};": None,
|
||||||
|
},
|
||||||
|
imports={
|
||||||
|
"react-dropzone": "useDropzone",
|
||||||
|
**Imports.EVENTS,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# The file input to use.
|
||||||
|
upload = Input.create(type="file")
|
||||||
|
upload.special_props = [
|
||||||
|
Var(
|
||||||
|
_js_expr=f"{{...{input_props_unique_name}()}}",
|
||||||
|
_var_type=None,
|
||||||
|
_var_data=var_data,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# The dropzone to use.
|
||||||
|
zone = Box.create(
|
||||||
|
upload,
|
||||||
|
*children,
|
||||||
|
**{k: v for k, v in props.items() if k not in supported_props},
|
||||||
|
)
|
||||||
|
zone.special_props = [
|
||||||
|
Var(
|
||||||
|
_js_expr=f"{{...{root_props_unique_name}()}}",
|
||||||
|
_var_type=None,
|
||||||
|
_var_data=var_data,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
return super().create(
|
return super().create(
|
||||||
zone,
|
zone,
|
||||||
**upload_props,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -295,11 +356,6 @@ class Upload(MemoizationLeaf):
|
|||||||
return (arg_value[0], placeholder)
|
return (arg_value[0], placeholder)
|
||||||
return arg_value
|
return arg_value
|
||||||
|
|
||||||
def _render(self):
|
|
||||||
out = super()._render()
|
|
||||||
out.args = ("getRootProps", "getInputProps")
|
|
||||||
return out
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
|
def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
|
||||||
return {
|
return {
|
||||||
|
@ -6,7 +6,11 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, ClassVar, Dict, List, Optional, Union, overload
|
from typing import Any, ClassVar, Dict, List, Optional, Union, overload
|
||||||
|
|
||||||
from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
|
from reflex.components.component import (
|
||||||
|
Component,
|
||||||
|
ComponentNamespace,
|
||||||
|
MemoizationLeaf,
|
||||||
|
)
|
||||||
from reflex.constants import Dirs
|
from reflex.constants import Dirs
|
||||||
from reflex.event import (
|
from reflex.event import (
|
||||||
CallableEventSpec,
|
CallableEventSpec,
|
||||||
|
@ -63,6 +63,9 @@ def load_dynamic_serializer():
|
|||||||
"""
|
"""
|
||||||
# Causes a circular import, so we import here.
|
# Causes a circular import, so we import here.
|
||||||
from reflex.compiler import templates, utils
|
from reflex.compiler import templates, utils
|
||||||
|
from reflex.components.base.bare import Bare
|
||||||
|
|
||||||
|
component = Bare.create(Var.create(component))
|
||||||
|
|
||||||
rendered_components = {}
|
rendered_components = {}
|
||||||
# Include dynamic imports in the shared component.
|
# Include dynamic imports in the shared component.
|
||||||
@ -127,14 +130,15 @@ def load_dynamic_serializer():
|
|||||||
module_code_lines[ix] = line.replace(
|
module_code_lines[ix] = line.replace(
|
||||||
"export function", "export default function", 1
|
"export function", "export default function", 1
|
||||||
)
|
)
|
||||||
|
line_stripped = line.strip()
|
||||||
|
if line_stripped.startswith("{") and line_stripped.endswith("}"):
|
||||||
|
module_code_lines[ix] = line_stripped[1:-1]
|
||||||
|
|
||||||
module_code_lines.insert(0, "const React = window.__reflex.react;")
|
module_code_lines.insert(0, "const React = window.__reflex.react;")
|
||||||
|
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
[
|
[
|
||||||
"//__reflex_evaluate",
|
"//__reflex_evaluate",
|
||||||
"/** @jsx jsx */",
|
|
||||||
"const { jsx } = window.__reflex['@emotion/react']",
|
|
||||||
*module_code_lines,
|
*module_code_lines,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from reflex.event import EventChain
|
from reflex.event import EventChain
|
||||||
from reflex.utils import format, types
|
from reflex.utils import format, types
|
||||||
@ -23,9 +23,6 @@ class Tag:
|
|||||||
# The inner contents of the tag.
|
# The inner contents of the tag.
|
||||||
contents: str = ""
|
contents: str = ""
|
||||||
|
|
||||||
# Args to pass to the tag.
|
|
||||||
args: Optional[Tuple[str, ...]] = None
|
|
||||||
|
|
||||||
# Special props that aren't key value pairs.
|
# Special props that aren't key value pairs.
|
||||||
special_props: List[Var] = dataclasses.field(default_factory=list)
|
special_props: List[Var] = dataclasses.field(default_factory=list)
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ def FormSubmitName(form_component):
|
|||||||
on_change=rx.console_log,
|
on_change=rx.console_log,
|
||||||
),
|
),
|
||||||
rx.button("Submit", type_="submit"),
|
rx.button("Submit", type_="submit"),
|
||||||
rx.icon_button(FormState.val, icon=rx.icon(tag="plus")),
|
rx.icon_button(rx.icon(tag="plus")),
|
||||||
),
|
),
|
||||||
on_submit=FormState.form_submit,
|
on_submit=FormState.form_submit,
|
||||||
custom_attrs={"action": "/invalid"},
|
custom_attrs={"action": "/invalid"},
|
||||||
|
@ -793,8 +793,8 @@ def test_var_operations(driver, var_operations: AppHarness):
|
|||||||
("foreach_list_ix", "1\n2"),
|
("foreach_list_ix", "1\n2"),
|
||||||
("foreach_list_nested", "1\n1\n2"),
|
("foreach_list_nested", "1\n1\n2"),
|
||||||
# rx.memo component with state
|
# rx.memo component with state
|
||||||
("memo_comp", "[1,2]10"),
|
("memo_comp", "1210"),
|
||||||
("memo_comp_nested", "[3,4]5"),
|
("memo_comp_nested", "345"),
|
||||||
# foreach in a match
|
# foreach in a match
|
||||||
("foreach_in_match", "first\nsecond\nthird"),
|
("foreach_in_match", "first\nsecond\nthird"),
|
||||||
]
|
]
|
||||||
|
@ -1,205 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
import reflex as rx
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def upload_root_component():
|
|
||||||
"""A test upload component function.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A test upload component function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def upload_root_component():
|
|
||||||
return rx.upload.root(
|
|
||||||
rx.button("select file"),
|
|
||||||
rx.text("Drag and drop files here or click to select files"),
|
|
||||||
border="1px dotted black",
|
|
||||||
)
|
|
||||||
|
|
||||||
return upload_root_component()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def upload_component():
|
|
||||||
"""A test upload component function.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A test upload component function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def upload_component():
|
|
||||||
return rx.upload(
|
|
||||||
rx.button("select file"),
|
|
||||||
rx.text("Drag and drop files here or click to select files"),
|
|
||||||
border="1px dotted black",
|
|
||||||
)
|
|
||||||
|
|
||||||
return upload_component()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def upload_component_id_special():
|
|
||||||
def upload_component():
|
|
||||||
return rx.upload(
|
|
||||||
rx.button("select file"),
|
|
||||||
rx.text("Drag and drop files here or click to select files"),
|
|
||||||
border="1px dotted black",
|
|
||||||
id="#spec!`al-_98ID",
|
|
||||||
)
|
|
||||||
|
|
||||||
return upload_component()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def upload_component_with_props():
|
|
||||||
"""A test upload component with props function.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A test upload component with props function.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def upload_component_with_props():
|
|
||||||
return rx.upload(
|
|
||||||
rx.button("select file"),
|
|
||||||
rx.text("Drag and drop files here or click to select files"),
|
|
||||||
border="1px dotted black",
|
|
||||||
no_drag=True,
|
|
||||||
max_files=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
return upload_component_with_props()
|
|
||||||
|
|
||||||
|
|
||||||
def test_upload_root_component_render(upload_root_component):
|
|
||||||
"""Test that the render function is set correctly.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
upload_root_component: component fixture
|
|
||||||
"""
|
|
||||||
upload = upload_root_component.render()
|
|
||||||
|
|
||||||
# upload
|
|
||||||
assert upload["name"] == "ReactDropzone"
|
|
||||||
assert upload["props"] == [
|
|
||||||
'id={"default"}',
|
|
||||||
"multiple={true}",
|
|
||||||
"onDrop={e => setFilesById(filesById => {\n"
|
|
||||||
" const updatedFilesById = Object.assign({}, filesById);\n"
|
|
||||||
' updatedFilesById["default"] = e;\n'
|
|
||||||
" return updatedFilesById;\n"
|
|
||||||
" })\n"
|
|
||||||
" }",
|
|
||||||
"ref={ref_default}",
|
|
||||||
]
|
|
||||||
assert upload["args"] == ("getRootProps", "getInputProps")
|
|
||||||
|
|
||||||
# box inside of upload
|
|
||||||
[box] = upload["children"]
|
|
||||||
assert box["name"] == "RadixThemesBox"
|
|
||||||
assert box["props"] == [
|
|
||||||
'className={"rx-Upload"}',
|
|
||||||
'css={({ ["border"] : "1px dotted black" })}',
|
|
||||||
"{...getRootProps()}",
|
|
||||||
]
|
|
||||||
|
|
||||||
# input, button and text inside of box
|
|
||||||
[input, button, text] = box["children"]
|
|
||||||
assert input["name"] == "input"
|
|
||||||
assert input["props"] == ['type={"file"}', "{...getInputProps()}"]
|
|
||||||
|
|
||||||
assert button["name"] == "RadixThemesButton"
|
|
||||||
assert button["children"][0]["contents"] == '{"select file"}'
|
|
||||||
|
|
||||||
assert text["name"] == "RadixThemesText"
|
|
||||||
assert (
|
|
||||||
text["children"][0]["contents"]
|
|
||||||
== '{"Drag and drop files here or click to select files"}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_upload_component_render(upload_component):
|
|
||||||
"""Test that the render function is set correctly.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
upload_component: component fixture
|
|
||||||
"""
|
|
||||||
upload = upload_component.render()
|
|
||||||
|
|
||||||
# upload
|
|
||||||
assert upload["name"] == "ReactDropzone"
|
|
||||||
assert upload["props"] == [
|
|
||||||
'id={"default"}',
|
|
||||||
"multiple={true}",
|
|
||||||
"onDrop={e => setFilesById(filesById => {\n"
|
|
||||||
" const updatedFilesById = Object.assign({}, filesById);\n"
|
|
||||||
' updatedFilesById["default"] = e;\n'
|
|
||||||
" return updatedFilesById;\n"
|
|
||||||
" })\n"
|
|
||||||
" }",
|
|
||||||
"ref={ref_default}",
|
|
||||||
]
|
|
||||||
assert upload["args"] == ("getRootProps", "getInputProps")
|
|
||||||
|
|
||||||
# box inside of upload
|
|
||||||
[box] = upload["children"]
|
|
||||||
assert box["name"] == "RadixThemesBox"
|
|
||||||
assert box["props"] == [
|
|
||||||
'className={"rx-Upload"}',
|
|
||||||
'css={({ ["border"] : "1px dotted black", ["padding"] : "5em", ["textAlign"] : "center" })}',
|
|
||||||
"{...getRootProps()}",
|
|
||||||
]
|
|
||||||
|
|
||||||
# input, button and text inside of box
|
|
||||||
[input, button, text] = box["children"]
|
|
||||||
assert input["name"] == "input"
|
|
||||||
assert input["props"] == ['type={"file"}', "{...getInputProps()}"]
|
|
||||||
|
|
||||||
assert button["name"] == "RadixThemesButton"
|
|
||||||
assert button["children"][0]["contents"] == '{"select file"}'
|
|
||||||
|
|
||||||
assert text["name"] == "RadixThemesText"
|
|
||||||
assert (
|
|
||||||
text["children"][0]["contents"]
|
|
||||||
== '{"Drag and drop files here or click to select files"}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_upload_component_with_props_render(upload_component_with_props):
|
|
||||||
"""Test that the render function is set correctly.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
upload_component_with_props: component fixture
|
|
||||||
"""
|
|
||||||
upload = upload_component_with_props.render()
|
|
||||||
|
|
||||||
assert upload["props"] == [
|
|
||||||
'id={"default"}',
|
|
||||||
"maxFiles={2}",
|
|
||||||
"multiple={true}",
|
|
||||||
"noDrag={true}",
|
|
||||||
"onDrop={e => setFilesById(filesById => {\n"
|
|
||||||
" const updatedFilesById = Object.assign({}, filesById);\n"
|
|
||||||
' updatedFilesById["default"] = e;\n'
|
|
||||||
" return updatedFilesById;\n"
|
|
||||||
" })\n"
|
|
||||||
" }",
|
|
||||||
"ref={ref_default}",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_upload_component_id_with_special_chars(upload_component_id_special):
|
|
||||||
upload = upload_component_id_special.render()
|
|
||||||
|
|
||||||
assert upload["props"] == [
|
|
||||||
r'id={"#spec!`al-_98ID"}',
|
|
||||||
"multiple={true}",
|
|
||||||
"onDrop={e => setFilesById(filesById => {\n"
|
|
||||||
" const updatedFilesById = Object.assign({}, filesById);\n"
|
|
||||||
' updatedFilesById["#spec!`al-_98ID"] = e;\n'
|
|
||||||
" return updatedFilesById;\n"
|
|
||||||
" })\n"
|
|
||||||
" }",
|
|
||||||
"ref={ref__spec_al__98ID}",
|
|
||||||
]
|
|
@ -642,21 +642,18 @@ def test_component_create_unallowed_types(children, test_component):
|
|||||||
"name": "Fragment",
|
"name": "Fragment",
|
||||||
"props": [],
|
"props": [],
|
||||||
"contents": "",
|
"contents": "",
|
||||||
"args": None,
|
|
||||||
"special_props": [],
|
"special_props": [],
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"name": "RadixThemesText",
|
"name": "RadixThemesText",
|
||||||
"props": ['as={"p"}'],
|
"props": ['as={"p"}'],
|
||||||
"contents": "",
|
"contents": "",
|
||||||
"args": None,
|
|
||||||
"special_props": [],
|
"special_props": [],
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
"props": [],
|
"props": [],
|
||||||
"contents": '{"first_text"}',
|
"contents": '{"first_text"}',
|
||||||
"args": None,
|
|
||||||
"special_props": [],
|
"special_props": [],
|
||||||
"children": [],
|
"children": [],
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
@ -671,15 +668,12 @@ def test_component_create_unallowed_types(children, test_component):
|
|||||||
(
|
(
|
||||||
(rx.text("first_text"), rx.text("second_text")),
|
(rx.text("first_text"), rx.text("second_text")),
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [],
|
"children": [],
|
||||||
"contents": '{"first_text"}',
|
"contents": '{"first_text"}',
|
||||||
@ -694,11 +688,9 @@ def test_component_create_unallowed_types(children, test_component):
|
|||||||
"special_props": [],
|
"special_props": [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [],
|
"children": [],
|
||||||
"contents": '{"second_text"}',
|
"contents": '{"second_text"}',
|
||||||
@ -722,15 +714,12 @@ def test_component_create_unallowed_types(children, test_component):
|
|||||||
(
|
(
|
||||||
(rx.text("first_text"), rx.box((rx.text("second_text"),))),
|
(rx.text("first_text"), rx.box((rx.text("second_text"),))),
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [],
|
"children": [],
|
||||||
"contents": '{"first_text"}',
|
"contents": '{"first_text"}',
|
||||||
@ -745,19 +734,15 @@ def test_component_create_unallowed_types(children, test_component):
|
|||||||
"special_props": [],
|
"special_props": [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"args": None,
|
|
||||||
"autofocus": False,
|
"autofocus": False,
|
||||||
"children": [],
|
"children": [],
|
||||||
"contents": '{"second_text"}',
|
"contents": '{"second_text"}',
|
||||||
@ -1117,10 +1102,10 @@ def test_component_with_only_valid_children(fixture, request):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"component,rendered",
|
"component,rendered",
|
||||||
[
|
[
|
||||||
(rx.text("hi"), '<RadixThemesText as={"p"}>\n {"hi"}\n</RadixThemesText>'),
|
(rx.text("hi"), '<RadixThemesText as={"p"}>\n\n{"hi"}\n</RadixThemesText>'),
|
||||||
(
|
(
|
||||||
rx.box(rx.heading("test", size="3")),
|
rx.box(rx.heading("test", size="3")),
|
||||||
'<RadixThemesBox>\n <RadixThemesHeading size={"3"}>\n {"test"}\n</RadixThemesHeading>\n</RadixThemesBox>',
|
'<RadixThemesBox>\n\n<RadixThemesHeading size={"3"}>\n\n{"test"}\n</RadixThemesHeading>\n</RadixThemesBox>',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user