Skip to content

Commit c0fcde0

Browse files
committed
Merge branch 'main' into fail-on-using-rxcond-on-event-handlers-or-event-spec
2 parents 4a67dff + d18989e commit c0fcde0

File tree

16 files changed

+1888
-1688
lines changed

16 files changed

+1888
-1688
lines changed

pyi_hashes.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"reflex/components/core/debounce.pyi": "affda049624c266c7d5620efa3b7041b",
2121
"reflex/components/core/html.pyi": "b12117b42ef79ee90b6b4dec50baeb86",
2222
"reflex/components/core/sticky.pyi": "c65131cf7c2312c68e1fddaa0cc27150",
23-
"reflex/components/core/upload.pyi": "53e06193fa23a603737bc49b1c6c2565",
23+
"reflex/components/core/upload.pyi": "4680da6f7b3df704a682cc6441b1ac18",
2424
"reflex/components/datadisplay/__init__.pyi": "cf087efa8b3960decc6b231cc986cfa9",
2525
"reflex/components/datadisplay/code.pyi": "3d8f0ab4c2f123d7f80d15c7ebc553d9",
2626
"reflex/components/datadisplay/dataeditor.pyi": "cb03d732e2fe771a8d46c7bcda671f92",
@@ -111,9 +111,9 @@
111111
"reflex/components/radix/themes/typography/heading.pyi": "5a3b0b8e44bda0fce22c6b1a1f25e68e",
112112
"reflex/components/radix/themes/typography/link.pyi": "45965d95b9f9b76f8f4a3084a5430194",
113113
"reflex/components/radix/themes/typography/text.pyi": "e6aa0ca43ebbd42701a3c72c0312032e",
114-
"reflex/components/react_player/audio.pyi": "972975ed0ba3e1dc4a867da20b11ae8e",
115-
"reflex/components/react_player/react_player.pyi": "63ffffbc24907103f797dcfd85894107",
116-
"reflex/components/react_player/video.pyi": "35ce5ad62e8bff17d9c09d27c362f8dc",
114+
"reflex/components/react_player/audio.pyi": "18fb682ec86d1b44682e1903dff11794",
115+
"reflex/components/react_player/react_player.pyi": "171d829b30c1c0c62e49e4a21cffe50f",
116+
"reflex/components/react_player/video.pyi": "5c93cfe85ba4dcadfddae94a2e36bb4e",
117117
"reflex/components/recharts/__init__.pyi": "a52c9055e37c6ee25ded15688d45e8a5",
118118
"reflex/components/recharts/cartesian.pyi": "9dd16c08abe5205c6c414474e2de2f79",
119119
"reflex/components/recharts/charts.pyi": "3570af4627c601d10ee37033f1b2329c",

reflex/app.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from starlette.datastructures import UploadFile as StarletteUploadFile
3030
from starlette.exceptions import HTTPException
3131
from starlette.middleware import cors
32-
from starlette.requests import Request
32+
from starlette.requests import ClientDisconnect, Request
3333
from starlette.responses import JSONResponse, Response, StreamingResponse
3434
from starlette.staticfiles import StaticFiles
3535
from typing_extensions import deprecated
@@ -1828,7 +1828,10 @@ async def upload_file(request: Request):
18281828
from reflex.utils.exceptions import UploadTypeError, UploadValueError
18291829

18301830
# Get the files from the request.
1831-
files = await request.form()
1831+
try:
1832+
files = await request.form()
1833+
except ClientDisconnect:
1834+
return Response() # user cancelled
18321835
files = files.getlist("files")
18331836
if not files:
18341837
raise UploadValueError("No files were uploaded.")

reflex/components/component.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
no_args_event_spec,
4444
parse_args_spec,
4545
run_script,
46+
unwrap_var_annotation,
4647
)
4748
from reflex.style import Style, format_as_emotion
4849
from reflex.utils import console, format, imports, types
@@ -1936,8 +1937,8 @@ def _register_custom_component(
19361937
prop: (
19371938
Var(
19381939
"",
1939-
_var_type=annotation,
1940-
)
1940+
_var_type=unwrap_var_annotation(annotation),
1941+
).guess_type()
19411942
if not types.safe_issubclass(annotation, EventHandler)
19421943
else EventSpec(handler=EventHandler(fn=lambda: []))
19431944
)

reflex/components/core/upload.py

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
MemoizationLeaf,
1414
StatefulComponent,
1515
)
16+
from reflex.components.core.cond import cond
1617
from reflex.components.el.elements.forms import Input
1718
from reflex.components.radix.themes.layout.box import Box
1819
from reflex.config import environment
@@ -28,6 +29,7 @@
2829
parse_args_spec,
2930
run_script,
3031
)
32+
from reflex.style import Style
3133
from reflex.utils import format
3234
from reflex.utils.imports import ImportVar
3335
from reflex.vars import VarData
@@ -231,6 +233,9 @@ class Upload(MemoizationLeaf):
231233
# Fired when files are dropped.
232234
on_drop: EventHandler[_on_drop_spec]
233235

236+
# Style rules to apply when actively dragging.
237+
drag_active_style: Style | None = None
238+
234239
@classmethod
235240
def create(cls, *children, **props) -> Component:
236241
"""Create an upload component.
@@ -266,25 +271,46 @@ def create(cls, *children, **props) -> Component:
266271
# If on_drop is not provided, save files to be uploaded later.
267272
upload_props["on_drop"] = upload_file(upload_props["id"])
268273
else:
269-
on_drop = upload_props["on_drop"]
270-
if isinstance(on_drop, (EventHandler, EventSpec)):
271-
# Call the lambda to get the event chain.
272-
on_drop = call_event_handler(on_drop, _on_drop_spec)
273-
elif isinstance(on_drop, Callable):
274-
# Call the lambda to get the event chain.
275-
on_drop = call_event_fn(on_drop, _on_drop_spec)
276-
if isinstance(on_drop, EventSpec):
277-
# Update the provided args for direct use with on_drop.
278-
on_drop = on_drop.with_args(
279-
args=tuple(
280-
cls._update_arg_tuple_for_on_drop(arg_value)
281-
for arg_value in on_drop.args
282-
),
283-
)
274+
on_drop = (
275+
[on_drop_prop]
276+
if not isinstance(on_drop_prop := upload_props["on_drop"], Sequence)
277+
else list(on_drop_prop)
278+
)
279+
for ix, event in enumerate(on_drop):
280+
if isinstance(event, (EventHandler, EventSpec)):
281+
# Call the lambda to get the event chain.
282+
event = call_event_handler(event, _on_drop_spec)
283+
elif isinstance(event, Callable):
284+
# Call the lambda to get the event chain.
285+
event = call_event_fn(event, _on_drop_spec)
286+
if isinstance(event, EventSpec):
287+
# Update the provided args for direct use with on_drop.
288+
event = event.with_args(
289+
args=tuple(
290+
cls._update_arg_tuple_for_on_drop(arg_value)
291+
for arg_value in event.args
292+
),
293+
)
294+
on_drop[ix] = event
284295
upload_props["on_drop"] = on_drop
285296

286297
input_props_unique_name = get_unique_variable_name()
287298
root_props_unique_name = get_unique_variable_name()
299+
is_drag_active_unique_name = get_unique_variable_name()
300+
drag_active_css_class_unique_name = get_unique_variable_name() + "-drag-active"
301+
302+
# Handle special style when dragging over the drop zone.
303+
if "drag_active_style" in props:
304+
props.setdefault("style", Style())[
305+
f"&:where(.{drag_active_css_class_unique_name})"
306+
] = props.pop("drag_active_style")
307+
props["class_name"].append(
308+
cond(
309+
Var(is_drag_active_unique_name),
310+
drag_active_css_class_unique_name,
311+
"",
312+
),
313+
)
288314

289315
event_var, callback_str = StatefulComponent._get_memoized_event_triggers(
290316
GhostUpload.create(on_drop=upload_props["on_drop"])
@@ -303,7 +329,13 @@ def create(cls, *children, **props) -> Component:
303329
}
304330
)
305331

306-
left_side = f"const {{getRootProps: {root_props_unique_name}, getInputProps: {input_props_unique_name}}} "
332+
left_side = (
333+
"const { "
334+
f"getRootProps: {root_props_unique_name}, "
335+
f"getInputProps: {input_props_unique_name}, "
336+
f"isDragActive: {is_drag_active_unique_name}"
337+
"}"
338+
)
307339
right_side = f"useDropzone({use_dropzone_arguments!s})"
308340

309341
var_data = VarData.merge(

reflex/components/react_player/react_player.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import TypedDict
5+
from typing import Any, TypedDict
66

77
from reflex.components.component import NoSSRComponent
88
from reflex.event import EventHandler, no_args_event_spec, passthrough_event_spec
@@ -50,12 +50,6 @@ class ReactPlayer(NoSSRComponent):
5050
# Mutes the player
5151
muted: Var[bool]
5252

53-
# Set the width of the player: ex:640px
54-
width: Var[str]
55-
56-
# Set the height of the player: ex:640px
57-
height: Var[str]
58-
5953
# Called when media is loaded and ready to play. If playing is set to true, media will play immediately.
6054
on_ready: EventHandler[no_args_event_spec]
6155

@@ -103,3 +97,23 @@ class ReactPlayer(NoSSRComponent):
10397

10498
# Called when picture-in-picture mode is disabled.
10599
on_disable_pip: EventHandler[no_args_event_spec]
100+
101+
def _render(self, props: dict[str, Any] | None = None):
102+
"""Render the component. Adds width and height set to None because
103+
react-player will set them to some random value that overrides the
104+
css width and height.
105+
106+
Args:
107+
props: The props to pass to the component.
108+
109+
Returns:
110+
The rendered component.
111+
"""
112+
return (
113+
super()
114+
._render(props)
115+
.add_props(
116+
width=Var.create(None),
117+
height=Var.create(None),
118+
)
119+
)

reflex/constants/installer.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ def _determine_nextjs_version() -> str:
8686
return default_version
8787

8888

89+
def _determine_react_version() -> str:
90+
default_version = "19.1.0"
91+
if (version := os.getenv("REACT_VERSION")) and version != default_version:
92+
from reflex.utils import console
93+
94+
console.warn(
95+
f"You have requested react@{version} but the supported version is {default_version}, abandon all hope ye who enter here."
96+
)
97+
return version
98+
return default_version
99+
100+
89101
class PackageJson(SimpleNamespace):
90102
"""Constants used to build the package.json file."""
91103

@@ -99,15 +111,17 @@ class Commands(SimpleNamespace):
99111

100112
PATH = "package.json"
101113

114+
_react_version = _determine_react_version()
115+
102116
DEPENDENCIES = {
103117
"@emotion/react": "11.14.0",
104118
"axios": "1.9.0",
105119
"json5": "2.2.3",
106120
"next": _determine_nextjs_version(),
107121
"next-sitemap": "4.2.3",
108122
"next-themes": "0.4.6",
109-
"react": "19.1.0",
110-
"react-dom": "19.1.0",
123+
"react": _react_version,
124+
"react-dom": _react_version,
111125
"react-focus-lock": "2.13.6",
112126
"socket.io-client": "4.8.1",
113127
"universal-cookie": "7.2.2",
@@ -119,5 +133,5 @@ class Commands(SimpleNamespace):
119133
}
120134
OVERRIDES = {
121135
# This should always match the `react` version in DEPENDENCIES for recharts compatibility.
122-
"react-is": "19.1.0"
136+
"react-is": _react_version
123137
}

reflex/event.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2087,6 +2087,30 @@ def wrapper(
20872087
setattr(func, BACKGROUND_TASK_MARKER, True)
20882088
if getattr(func, "__name__", "").startswith("_"):
20892089
raise ValueError("Event handlers cannot be private.")
2090+
2091+
qualname: str | None = getattr(func, "__qualname__", None)
2092+
2093+
if qualname and (
2094+
len(func_path := qualname.split(".")) == 1
2095+
or func_path[-2] == "<locals>"
2096+
):
2097+
from reflex.state import BaseState
2098+
2099+
types = get_type_hints(func)
2100+
state_arg_name = next(iter(inspect.signature(func).parameters), None)
2101+
state_cls = state_arg_name and types.get(state_arg_name)
2102+
if state_cls and issubclass(state_cls, BaseState):
2103+
name = (
2104+
(func.__module__ + "." + qualname)
2105+
.replace(".", "_")
2106+
.replace("<locals>", "_")
2107+
.removeprefix("_")
2108+
)
2109+
object.__setattr__(func, "__name__", name)
2110+
object.__setattr__(func, "__qualname__", name)
2111+
state_cls._add_event_handler(name, func)
2112+
return getattr(state_cls, name)
2113+
20902114
return func # pyright: ignore [reportReturnType]
20912115

20922116
if func is not None:

0 commit comments

Comments
 (0)