Skip to content

Commit 2295879

Browse files
committed
Merge remote-tracking branch 'origin/main' into release/reflex-0.8.23
2 parents 4864f89 + 2907722 commit 2295879

File tree

14 files changed

+964
-31
lines changed

14 files changed

+964
-31
lines changed

.github/workflows/integration_tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ jobs:
122122
repository: reflex-dev/reflex-web
123123
ref: main
124124
path: reflex-web
125+
submodules: recursive
125126

126127
- name: Compile pyproject.toml into requirements.txt
127128
working-directory: ./reflex-web
@@ -189,6 +190,7 @@ jobs:
189190
repository: reflex-dev/reflex-web
190191
ref: main
191192
path: reflex-web
193+
submodules: recursive
192194
- name: Compile pyproject.toml into requirements.txt
193195
working-directory: ./reflex-web
194196
run: |

pyi_hashes.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"reflex/__init__.pyi": "b304ed6f7a2fa028a194cad81bd83112",
2+
"reflex/__init__.pyi": "0a3ae880e256b9fd3b960e12a2cb51a7",
33
"reflex/components/__init__.pyi": "ac05995852baa81062ba3d18fbc489fb",
44
"reflex/components/base/__init__.pyi": "16e47bf19e0d62835a605baa3d039c5a",
55
"reflex/components/base/app_wrap.pyi": "22e94feaa9fe675bcae51c412f5b67f1",

reflex/.templates/web/utils/helpers/paste.js

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect } from "react";
1+
import { useEffect, useRef } from "react";
22

33
const handle_paste_data = (clipboardData) =>
44
new Promise((resolve, reject) => {
@@ -35,25 +35,71 @@ const handle_paste_data = (clipboardData) =>
3535
});
3636

3737
export default function usePasteHandler(target_ids, event_actions, on_paste) {
38-
return useEffect(() => {
38+
const onPasteRef = useRef(on_paste);
39+
const eventActionsRef = useRef(event_actions);
40+
41+
useEffect(() => {
42+
onPasteRef.current = on_paste;
43+
}, [on_paste]);
44+
useEffect(() => {
45+
eventActionsRef.current = event_actions;
46+
}, [event_actions]);
47+
48+
useEffect(() => {
3949
const handle_paste = (_ev) => {
40-
event_actions.preventDefault && _ev.preventDefault();
41-
event_actions.stopPropagation && _ev.stopPropagation();
42-
handle_paste_data(_ev.clipboardData).then(on_paste);
50+
eventActionsRef.current?.preventDefault && _ev.preventDefault();
51+
eventActionsRef.current?.stopPropagation && _ev.stopPropagation();
52+
handle_paste_data(_ev.clipboardData).then(onPasteRef.current);
4353
};
44-
const targets = target_ids
45-
.map((id) => document.getElementById(id))
46-
.filter((element) => !!element);
47-
if (target_ids.length === 0) {
48-
targets.push(document);
49-
}
50-
targets.forEach((target) =>
51-
target.addEventListener("paste", handle_paste, false),
52-
);
53-
return () => {
54+
55+
let cleanupListeners = null;
56+
let observer = null;
57+
58+
const attachListeners = (targets) => {
5459
targets.forEach((target) =>
55-
target.removeEventListener("paste", handle_paste, false),
60+
target.addEventListener("paste", handle_paste, false),
5661
);
62+
return () => {
63+
targets.forEach((target) =>
64+
target.removeEventListener("paste", handle_paste, false),
65+
);
66+
};
5767
};
58-
});
68+
69+
const tryAttach = () => {
70+
if (target_ids.length === 0) {
71+
cleanupListeners = attachListeners([document]);
72+
return true;
73+
}
74+
const targets = target_ids
75+
.map((id) => document.getElementById(id))
76+
.filter((element) => !!element);
77+
78+
if (targets.length === target_ids.length) {
79+
cleanupListeners = attachListeners(targets);
80+
return true;
81+
}
82+
83+
return false;
84+
};
85+
86+
if (!tryAttach()) {
87+
observer = new MutationObserver(() => {
88+
if (tryAttach()) {
89+
observer.disconnect();
90+
observer = null;
91+
}
92+
});
93+
observer.observe(document.body, { childList: true, subtree: true });
94+
}
95+
96+
return () => {
97+
if (observer) {
98+
observer.disconnect();
99+
}
100+
if (cleanupListeners) {
101+
cleanupListeners();
102+
}
103+
};
104+
}, [target_ids]);
59105
}

reflex/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@
336336
"State",
337337
"dynamic",
338338
],
339+
"istate.shared": ["SharedState"],
339340
"istate.wrappers": ["get_state"],
340341
"style": ["Style", "toggle_color_mode"],
341342
"utils.imports": ["ImportDict", "ImportVar"],

reflex/app.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,13 +1562,17 @@ def all_routes(_request: Request) -> Response:
15621562

15631563
@contextlib.asynccontextmanager
15641564
async def modify_state(
1565-
self, token: str, background: bool = False
1565+
self,
1566+
token: str,
1567+
background: bool = False,
1568+
previous_dirty_vars: dict[str, set[str]] | None = None,
15661569
) -> AsyncIterator[BaseState]:
15671570
"""Modify the state out of band.
15681571
15691572
Args:
15701573
token: The token to modify the state for.
15711574
background: Whether the modification is happening in a background task.
1575+
previous_dirty_vars: Vars that are considered dirty from a previous operation.
15721576
15731577
Yields:
15741578
The state to modify.
@@ -1581,7 +1585,9 @@ async def modify_state(
15811585
raise RuntimeError(msg)
15821586

15831587
# Get exclusive access to the state.
1584-
async with self.state_manager.modify_state(token) as state:
1588+
async with self.state_manager.modify_state_with_links(
1589+
token, previous_dirty_vars=previous_dirty_vars
1590+
) as state:
15851591
# No other event handler can modify the state while in this context.
15861592
yield state
15871593
delta = await state._get_resolved_delta()
@@ -1769,7 +1775,7 @@ async def process(
17691775
constants.RouteVar.CLIENT_IP: client_ip,
17701776
})
17711777
# Get the state for the session exclusively.
1772-
async with app.state_manager.modify_state(
1778+
async with app.state_manager.modify_state_with_links(
17731779
event.substate_token, event=event
17741780
) as state:
17751781
# When this is a brand new instance of the state, signal the
@@ -2003,7 +2009,9 @@ async def _ndjson_updates():
20032009
Each state update as JSON followed by a new line.
20042010
"""
20052011
# Process the event.
2006-
async with app.state_manager.modify_state(event.substate_token) as state:
2012+
async with app.state_manager.modify_state_with_links(
2013+
event.substate_token
2014+
) as state:
20072015
async for update in state._process(event):
20082016
# Postprocess the event.
20092017
update = await app._postprocess(state, event, update)

reflex/compiler/templates.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,9 @@ def vite_config_template(
585585
enableNativePlugin: false,
586586
hmr: {"true" if experimental_hmr else "false"},
587587
}},
588+
legacy: {{
589+
inconsistentCjsInterop: true,
590+
}},
588591
server: {{
589592
port: process.env.PORT,
590593
hmr: {"true" if hmr else "false"},

reflex/istate/manager/__init__.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,35 @@ async def modify_state(
114114
"""
115115
yield self.state()
116116

117+
@contextlib.asynccontextmanager
118+
async def modify_state_with_links(
119+
self,
120+
token: str,
121+
previous_dirty_vars: dict[str, set[str]] | None = None,
122+
**context: Unpack[StateModificationContext],
123+
) -> AsyncIterator[BaseState]:
124+
"""Modify the state for a token, including linked substates, while holding exclusive lock.
125+
126+
Args:
127+
token: The token to modify the state for.
128+
previous_dirty_vars: The previously dirty vars for linked states.
129+
context: The state modification context.
130+
131+
Yields:
132+
The state for the token with linked states patched in.
133+
"""
134+
async with self.modify_state(token, **context) as root_state:
135+
if getattr(root_state, "_reflex_internal_links", None) is not None:
136+
from reflex.istate.shared import SharedStateBaseInternal
137+
138+
shared_state = await root_state.get_state(SharedStateBaseInternal)
139+
async with shared_state._modify_linked_states(
140+
previous_dirty_vars=previous_dirty_vars
141+
) as _:
142+
yield root_state
143+
else:
144+
yield root_state
145+
117146
async def close(self): # noqa: B027
118147
"""Close the state manager."""
119148

reflex/istate/proxy.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -560,11 +560,15 @@ def __getattr__(self, __name: str) -> Any:
560560
)
561561

562562
if (
563-
not isinstance(self.__wrapped__, Base)
564-
or __name not in NEVER_WRAP_BASE_ATTRS
565-
) and hasattr(value, "__func__"):
563+
(
564+
not isinstance(self.__wrapped__, Base)
565+
or __name not in NEVER_WRAP_BASE_ATTRS
566+
)
567+
and (func := getattr(value, "__func__", None)) is not None
568+
and not inspect.isclass(getattr(value, "__self__", None))
569+
):
566570
# Rebind `self` to the proxy on methods to capture nested mutations.
567-
return functools.partial(value.__func__, self) # pyright: ignore [reportFunctionMemberAccess, reportAttributeAccessIssue]
571+
return functools.partial(func, self)
568572

569573
if is_mutable_type(type(value)) and __name not in (
570574
"__wrapped__",

0 commit comments

Comments
 (0)