Compare commits
5 Commits
lendemor/f
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
98f50811f9 | ||
![]() |
ee03415894 | ||
![]() |
8943341605 | ||
![]() |
836e8f8ce9 | ||
![]() |
e891dbe6b9 |
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "reflex"
|
name = "reflex"
|
||||||
version = "0.7.1dev1"
|
version = "0.7.2dev1"
|
||||||
description = "Web apps in pure Python."
|
description = "Web apps in pure Python."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
authors = [
|
authors = [
|
||||||
|
@ -15,7 +15,13 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
{% for package, version in dev_dependencies.items() %}
|
{% for package, version in dev_dependencies.items() %}
|
||||||
"{{ package }}": "{{ version }}"{% if not loop.last %},{% endif %}
|
"{{ package }}": "{{ version }}"{% if not loop.last %},{% endif %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
{% for package, version in overrides.items() %}
|
||||||
|
"{{ package }}": "{{ version }}"{% if not loop.last %},{% endif %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -78,9 +78,9 @@ export function UploadFilesProvider({ children }) {
|
|||||||
return newFilesById
|
return newFilesById
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<UploadFilesContext.Provider value={[filesById, setFilesById]}>
|
<UploadFilesContext value={[filesById, setFilesById]}>
|
||||||
{children}
|
{children}
|
||||||
</UploadFilesContext.Provider>
|
</UploadFilesContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,9 +92,9 @@ export function EventLoopProvider({ children }) {
|
|||||||
clientStorage,
|
clientStorage,
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<EventLoopContext.Provider value={[addEvents, connectErrors]}>
|
<EventLoopContext value={[addEvents, connectErrors]}>
|
||||||
{children}
|
{children}
|
||||||
</EventLoopContext.Provider>
|
</EventLoopContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,13 +112,13 @@ export function StateProvider({ children }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
{% for state_name in initial_state %}
|
{% for state_name in initial_state %}
|
||||||
<StateContexts.{{state_name|var_name}}.Provider value={ {{state_name|var_name}} }>
|
<StateContexts.{{state_name|var_name}} value={ {{state_name|var_name}} }>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<DispatchContext.Provider value={dispatchers}>
|
<DispatchContext value={dispatchers}>
|
||||||
{children}
|
{children}
|
||||||
</DispatchContext.Provider>
|
</DispatchContext>
|
||||||
{% for state_name in initial_state|reverse %}
|
{% for state_name in initial_state|reverse %}
|
||||||
</StateContexts.{{state_name|var_name}}.Provider>
|
</StateContexts.{{state_name|var_name}}>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -36,17 +36,17 @@ export default function RadixThemesColorModeProvider({ children }) {
|
|||||||
const allowedModes = ["light", "dark", "system"];
|
const allowedModes = ["light", "dark", "system"];
|
||||||
if (!allowedModes.includes(mode)) {
|
if (!allowedModes.includes(mode)) {
|
||||||
console.error(
|
console.error(
|
||||||
`Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".`
|
`Invalid color mode "${mode}". Defaulting to "${defaultColorMode}".`,
|
||||||
);
|
);
|
||||||
mode = defaultColorMode;
|
mode = defaultColorMode;
|
||||||
}
|
}
|
||||||
setTheme(mode);
|
setTheme(mode);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ColorModeContext.Provider
|
<ColorModeContext
|
||||||
value={{ rawColorMode, resolvedColorMode, toggleColorMode, setColorMode }}
|
value={{ rawColorMode, resolvedColorMode, toggleColorMode, setColorMode }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ColorModeContext.Provider>
|
</ColorModeContext>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ from reflex.components.core.client_side_routing import (
|
|||||||
from reflex.components.core.sticky import sticky
|
from reflex.components.core.sticky import sticky
|
||||||
from reflex.components.core.upload import Upload, get_upload_dir
|
from reflex.components.core.upload import Upload, get_upload_dir
|
||||||
from reflex.components.radix import themes
|
from reflex.components.radix import themes
|
||||||
|
from reflex.components.sonner.toast import toast
|
||||||
from reflex.config import ExecutorType, environment, get_config
|
from reflex.config import ExecutorType, environment, get_config
|
||||||
from reflex.event import (
|
from reflex.event import (
|
||||||
_EVENT_FIELDS,
|
_EVENT_FIELDS,
|
||||||
@ -84,7 +85,6 @@ from reflex.event import (
|
|||||||
EventType,
|
EventType,
|
||||||
IndividualEventType,
|
IndividualEventType,
|
||||||
get_hydrate_event,
|
get_hydrate_event,
|
||||||
window_alert,
|
|
||||||
)
|
)
|
||||||
from reflex.model import Model, get_db_status
|
from reflex.model import Model, get_db_status
|
||||||
from reflex.page import DECORATED_PAGES
|
from reflex.page import DECORATED_PAGES
|
||||||
@ -144,7 +144,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
|||||||
EventSpec: The window alert event.
|
EventSpec: The window alert event.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from reflex.components.sonner.toast import Toaster, toast
|
from reflex.components.sonner.toast import toast
|
||||||
|
|
||||||
error = traceback.format_exc()
|
error = traceback.format_exc()
|
||||||
|
|
||||||
@ -155,18 +155,16 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
|||||||
if is_prod_mode()
|
if is_prod_mode()
|
||||||
else [f"{type(exception).__name__}: {exception}.", "See logs for details."]
|
else [f"{type(exception).__name__}: {exception}.", "See logs for details."]
|
||||||
)
|
)
|
||||||
if Toaster.is_used:
|
|
||||||
return toast(
|
return toast(
|
||||||
"An error occurred.",
|
"An error occurred.",
|
||||||
level="error",
|
level="error",
|
||||||
description="<br/>".join(error_message),
|
fallback_to_alert=True,
|
||||||
position="top-center",
|
description="<br/>".join(error_message),
|
||||||
id="backend_error",
|
position="top-center",
|
||||||
style={"width": "500px"},
|
id="backend_error",
|
||||||
)
|
style={"width": "500px"},
|
||||||
else:
|
)
|
||||||
error_message.insert(0, "An error occurred.")
|
|
||||||
return window_alert("\n".join(error_message))
|
|
||||||
|
|
||||||
|
|
||||||
def extra_overlay_function() -> Optional[Component]:
|
def extra_overlay_function() -> Optional[Component]:
|
||||||
@ -414,7 +412,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
] = default_backend_exception_handler
|
] = default_backend_exception_handler
|
||||||
|
|
||||||
# Put the toast provider in the app wrap.
|
# Put the toast provider in the app wrap.
|
||||||
bundle_toaster: bool = True
|
toaster: Component | None = dataclasses.field(default_factory=toast.provider)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self) -> FastAPI | None:
|
def api(self) -> FastAPI | None:
|
||||||
@ -1100,10 +1098,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
should_compile = self._should_compile()
|
should_compile = self._should_compile()
|
||||||
|
|
||||||
if not should_compile:
|
if not should_compile:
|
||||||
if self.bundle_toaster:
|
|
||||||
from reflex.components.sonner.toast import Toaster
|
|
||||||
|
|
||||||
Toaster.is_used = True
|
|
||||||
with console.timing("Evaluate Pages (Backend)"):
|
with console.timing("Evaluate Pages (Backend)"):
|
||||||
for route in self._unevaluated_pages:
|
for route in self._unevaluated_pages:
|
||||||
console.debug(f"Evaluating page: {route}")
|
console.debug(f"Evaluating page: {route}")
|
||||||
@ -1133,20 +1127,6 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
+ adhoc_steps_without_executor,
|
+ adhoc_steps_without_executor,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.bundle_toaster:
|
|
||||||
from reflex.components.component import memo
|
|
||||||
from reflex.components.sonner.toast import toast
|
|
||||||
|
|
||||||
internal_toast_provider = toast.provider()
|
|
||||||
|
|
||||||
@memo
|
|
||||||
def memoized_toast_provider():
|
|
||||||
return internal_toast_provider
|
|
||||||
|
|
||||||
toast_provider = Fragment.create(memoized_toast_provider())
|
|
||||||
|
|
||||||
app_wrappers[(1, "ToasterProvider")] = toast_provider
|
|
||||||
|
|
||||||
with console.timing("Evaluate Pages (Frontend)"):
|
with console.timing("Evaluate Pages (Frontend)"):
|
||||||
performance_metrics: list[tuple[str, float]] = []
|
performance_metrics: list[tuple[str, float]] = []
|
||||||
for route in self._unevaluated_pages:
|
for route in self._unevaluated_pages:
|
||||||
@ -1207,6 +1187,17 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# Add the custom components from the page to the set.
|
# Add the custom components from the page to the set.
|
||||||
custom_components |= component._get_all_custom_components()
|
custom_components |= component._get_all_custom_components()
|
||||||
|
|
||||||
|
if (toaster := self.toaster) is not None:
|
||||||
|
from reflex.components.component import memo
|
||||||
|
|
||||||
|
@memo
|
||||||
|
def memoized_toast_provider():
|
||||||
|
return toaster
|
||||||
|
|
||||||
|
toast_provider = Fragment.create(memoized_toast_provider())
|
||||||
|
|
||||||
|
app_wrappers[(1, "ToasterProvider")] = toast_provider
|
||||||
|
|
||||||
# Add the app wraps to the app.
|
# Add the app wraps to the app.
|
||||||
for key, app_wrap in self.app_wraps.items():
|
for key, app_wrap in self.app_wraps.items():
|
||||||
component = app_wrap(self._state is not None)
|
component = app_wrap(self._state is not None)
|
||||||
|
@ -11,7 +11,9 @@ from reflex.vars.base import Var, get_unique_variable_name
|
|||||||
class AutoScroll(Div):
|
class AutoScroll(Div):
|
||||||
"""A div that automatically scrolls to the bottom when new content is added."""
|
"""A div that automatically scrolls to the bottom when new content is added."""
|
||||||
|
|
||||||
_memoization_mode = MemoizationMode(disposition=MemoizationDisposition.ALWAYS)
|
_memoization_mode = MemoizationMode(
|
||||||
|
disposition=MemoizationDisposition.ALWAYS, recursive=False
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children, **props):
|
def create(cls, *children, **props):
|
||||||
@ -44,7 +46,6 @@ class AutoScroll(Div):
|
|||||||
"""
|
"""
|
||||||
ref_name = self.get_ref()
|
ref_name = self.get_ref()
|
||||||
return [
|
return [
|
||||||
"const containerRef = useRef(null);",
|
|
||||||
"const wasNearBottom = useRef(false);",
|
"const wasNearBottom = useRef(false);",
|
||||||
"const hadScrollbar = useRef(false);",
|
"const hadScrollbar = useRef(false);",
|
||||||
f"""
|
f"""
|
||||||
@ -85,6 +86,8 @@ useEffect(() => {{
|
|||||||
const container = {ref_name}.current;
|
const container = {ref_name}.current;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
scrollToBottomIfNeeded();
|
||||||
|
|
||||||
// Create ResizeObserver to detect height changes
|
// Create ResizeObserver to detect height changes
|
||||||
const resizeObserver = new ResizeObserver(() => {{
|
const resizeObserver = new ResizeObserver(() => {{
|
||||||
scrollToBottomIfNeeded();
|
scrollToBottomIfNeeded();
|
||||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
|
from reflex.components.base.fragment import Fragment
|
||||||
from reflex.components.component import Component
|
from reflex.components.component import Component
|
||||||
from reflex.components.core.cond import cond
|
from reflex.components.core.cond import cond
|
||||||
from reflex.components.el.elements.typography import Div
|
from reflex.components.el.elements.typography import Div
|
||||||
@ -16,7 +17,7 @@ from reflex.components.radix.themes.components.dialog import (
|
|||||||
)
|
)
|
||||||
from reflex.components.radix.themes.layout.flex import Flex
|
from reflex.components.radix.themes.layout.flex import Flex
|
||||||
from reflex.components.radix.themes.typography.text import Text
|
from reflex.components.radix.themes.typography.text import Text
|
||||||
from reflex.components.sonner.toast import Toaster, ToastProps
|
from reflex.components.sonner.toast import ToastProps, toast_ref
|
||||||
from reflex.config import environment
|
from reflex.config import environment
|
||||||
from reflex.constants import Dirs, Hooks, Imports
|
from reflex.constants import Dirs, Hooks, Imports
|
||||||
from reflex.constants.compiler import CompileVars
|
from reflex.constants.compiler import CompileVars
|
||||||
@ -90,7 +91,7 @@ def default_connection_error() -> list[str | Var | Component]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ConnectionToaster(Toaster):
|
class ConnectionToaster(Fragment):
|
||||||
"""A connection toaster component."""
|
"""A connection toaster component."""
|
||||||
|
|
||||||
def add_hooks(self) -> list[str | Var]:
|
def add_hooks(self) -> list[str | Var]:
|
||||||
@ -113,11 +114,11 @@ class ConnectionToaster(Toaster):
|
|||||||
if environment.REFLEX_DOES_BACKEND_COLD_START.get():
|
if environment.REFLEX_DOES_BACKEND_COLD_START.get():
|
||||||
loading_message = Var.create("Backend is starting.")
|
loading_message = Var.create("Backend is starting.")
|
||||||
backend_is_loading_toast_var = Var(
|
backend_is_loading_toast_var = Var(
|
||||||
f"toast.loading({loading_message!s}, {{...toast_props, description: '', closeButton: false, onDismiss: () => setUserDismissed(true)}},)"
|
f"toast?.loading({loading_message!s}, {{...toast_props, description: '', closeButton: false, onDismiss: () => setUserDismissed(true)}},)"
|
||||||
)
|
)
|
||||||
backend_is_not_responding = Var.create("Backend is not responding.")
|
backend_is_not_responding = Var.create("Backend is not responding.")
|
||||||
backend_is_down_toast_var = Var(
|
backend_is_down_toast_var = Var(
|
||||||
f"toast.error({backend_is_not_responding!s}, {{...toast_props, description: '', onDismiss: () => setUserDismissed(true)}},)"
|
f"toast?.error({backend_is_not_responding!s}, {{...toast_props, description: '', onDismiss: () => setUserDismissed(true)}},)"
|
||||||
)
|
)
|
||||||
toast_var = Var(
|
toast_var = Var(
|
||||||
f"""
|
f"""
|
||||||
@ -138,10 +139,11 @@ setTimeout(() => {{
|
|||||||
f"Cannot connect to server: {connection_error}."
|
f"Cannot connect to server: {connection_error}."
|
||||||
)
|
)
|
||||||
toast_var = Var(
|
toast_var = Var(
|
||||||
f"toast.error({loading_message!s}, {{...toast_props, onDismiss: () => setUserDismissed(true)}},)"
|
f"toast?.error({loading_message!s}, {{...toast_props, onDismiss: () => setUserDismissed(true)}},)"
|
||||||
)
|
)
|
||||||
|
|
||||||
individual_hooks = [
|
individual_hooks = [
|
||||||
|
Var(f"const toast = {toast_ref};"),
|
||||||
f"const toast_props = {LiteralVar.create(props)!s};",
|
f"const toast_props = {LiteralVar.create(props)!s};",
|
||||||
"const [userDismissed, setUserDismissed] = useState(false);",
|
"const [userDismissed, setUserDismissed] = useState(false);",
|
||||||
"const [waitedForBackend, setWaitedForBackend] = useState(false);",
|
"const [waitedForBackend, setWaitedForBackend] = useState(false);",
|
||||||
@ -163,7 +165,7 @@ setTimeout(() => {{
|
|||||||
{toast_var!s}
|
{toast_var!s}
|
||||||
}}
|
}}
|
||||||
}} else {{
|
}} else {{
|
||||||
toast.dismiss("{toast_id}");
|
toast?.dismiss("{toast_id}");
|
||||||
setUserDismissed(false); // after reconnection reset dismissed state
|
setUserDismissed(false); // after reconnection reset dismissed state
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
@ -189,7 +191,6 @@ setTimeout(() => {{
|
|||||||
Returns:
|
Returns:
|
||||||
The connection toaster component.
|
The connection toaster component.
|
||||||
"""
|
"""
|
||||||
Toaster.is_used = True
|
|
||||||
return super().create(*children, **props)
|
return super().create(*children, **props)
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
from typing import Any, Dict, Literal, Optional, Union, overload
|
from typing import Any, Dict, Literal, Optional, Union, overload
|
||||||
|
|
||||||
|
from reflex.components.base.fragment import Fragment
|
||||||
from reflex.components.component import Component
|
from reflex.components.component import Component
|
||||||
from reflex.components.el.elements.typography import Div
|
from reflex.components.el.elements.typography import Div
|
||||||
from reflex.components.lucide.icon import Icon
|
from reflex.components.lucide.icon import Icon
|
||||||
from reflex.components.sonner.toast import Toaster, ToastProps
|
|
||||||
from reflex.constants.compiler import CompileVars
|
from reflex.constants.compiler import CompileVars
|
||||||
from reflex.event import EventType
|
from reflex.event import EventType
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
@ -41,48 +41,13 @@ class WebsocketTargetURL(Var):
|
|||||||
|
|
||||||
def default_connection_error() -> list[str | Var | Component]: ...
|
def default_connection_error() -> list[str | Var | Component]: ...
|
||||||
|
|
||||||
class ConnectionToaster(Toaster):
|
class ConnectionToaster(Fragment):
|
||||||
def add_hooks(self) -> list[str | Var]: ...
|
def add_hooks(self) -> list[str | Var]: ...
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
def create( # type: ignore
|
def create( # type: ignore
|
||||||
cls,
|
cls,
|
||||||
*children,
|
*children,
|
||||||
theme: Optional[Union[Var[str], str]] = None,
|
|
||||||
rich_colors: Optional[Union[Var[bool], bool]] = None,
|
|
||||||
expand: Optional[Union[Var[bool], bool]] = None,
|
|
||||||
visible_toasts: Optional[Union[Var[int], int]] = None,
|
|
||||||
position: Optional[
|
|
||||||
Union[
|
|
||||||
Literal[
|
|
||||||
"bottom-center",
|
|
||||||
"bottom-left",
|
|
||||||
"bottom-right",
|
|
||||||
"top-center",
|
|
||||||
"top-left",
|
|
||||||
"top-right",
|
|
||||||
],
|
|
||||||
Var[
|
|
||||||
Literal[
|
|
||||||
"bottom-center",
|
|
||||||
"bottom-left",
|
|
||||||
"bottom-right",
|
|
||||||
"top-center",
|
|
||||||
"top-left",
|
|
||||||
"top-right",
|
|
||||||
]
|
|
||||||
],
|
|
||||||
]
|
|
||||||
] = None,
|
|
||||||
close_button: Optional[Union[Var[bool], bool]] = None,
|
|
||||||
offset: Optional[Union[Var[str], str]] = None,
|
|
||||||
dir: Optional[Union[Var[str], str]] = None,
|
|
||||||
hotkey: Optional[Union[Var[str], str]] = None,
|
|
||||||
invert: Optional[Union[Var[bool], bool]] = None,
|
|
||||||
toast_options: Optional[Union[ToastProps, Var[ToastProps]]] = None,
|
|
||||||
gap: Optional[Union[Var[int], int]] = None,
|
|
||||||
loading_icon: Optional[Union[Icon, Var[Icon]]] = None,
|
|
||||||
pause_when_page_is_hidden: Optional[Union[Var[bool], bool]] = None,
|
|
||||||
style: Optional[Style] = None,
|
style: Optional[Style] = None,
|
||||||
key: Optional[Any] = None,
|
key: Optional[Any] = None,
|
||||||
id: Optional[Any] = None,
|
id: Optional[Any] = None,
|
||||||
@ -110,20 +75,6 @@ class ConnectionToaster(Toaster):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
*children: The children of the component.
|
*children: The children of the component.
|
||||||
theme: the theme of the toast
|
|
||||||
rich_colors: whether to show rich colors
|
|
||||||
expand: whether to expand the toast
|
|
||||||
visible_toasts: the number of toasts that are currently visible
|
|
||||||
position: the position of the toast
|
|
||||||
close_button: whether to show the close button
|
|
||||||
offset: offset of the toast
|
|
||||||
dir: directionality of the toast (default: ltr)
|
|
||||||
hotkey: Keyboard shortcut that will move focus to the toaster area.
|
|
||||||
invert: Dark toasts in light mode and vice versa.
|
|
||||||
toast_options: These will act as default options for all toasts. See toast() for all available options.
|
|
||||||
gap: Gap between toasts when expanded
|
|
||||||
loading_icon: Changes the default loading icon
|
|
||||||
pause_when_page_is_hidden: Pauses toast timers when the page is hidden, e.g., when the tab is backgrounded, the browser is minimized, or the OS is locked.
|
|
||||||
style: The style of the component.
|
style: The style of the component.
|
||||||
key: A unique key for the component.
|
key: A unique key for the component.
|
||||||
id: The id for the component.
|
id: The id for the component.
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, ClassVar, Literal, Optional, Union
|
from typing import Any, Literal, Optional, Union
|
||||||
|
|
||||||
from reflex.base import Base
|
from reflex.base import Base
|
||||||
from reflex.components.component import Component, ComponentNamespace
|
from reflex.components.component import Component, ComponentNamespace
|
||||||
@ -17,6 +17,7 @@ from reflex.utils.serializers import serializer
|
|||||||
from reflex.vars import VarData
|
from reflex.vars import VarData
|
||||||
from reflex.vars.base import LiteralVar, Var
|
from reflex.vars.base import LiteralVar, Var
|
||||||
from reflex.vars.function import FunctionVar
|
from reflex.vars.function import FunctionVar
|
||||||
|
from reflex.vars.number import ternary_operation
|
||||||
from reflex.vars.object import ObjectVar
|
from reflex.vars.object import ObjectVar
|
||||||
|
|
||||||
LiteralPosition = Literal[
|
LiteralPosition = Literal[
|
||||||
@ -217,9 +218,6 @@ class Toaster(Component):
|
|||||||
# Pauses toast timers when the page is hidden, e.g., when the tab is backgrounded, the browser is minimized, or the OS is locked.
|
# Pauses toast timers when the page is hidden, e.g., when the tab is backgrounded, the browser is minimized, or the OS is locked.
|
||||||
pause_when_page_is_hidden: Var[bool]
|
pause_when_page_is_hidden: Var[bool]
|
||||||
|
|
||||||
# Marked True when any Toast component is created.
|
|
||||||
is_used: ClassVar[bool] = False
|
|
||||||
|
|
||||||
def add_hooks(self) -> list[Var | str]:
|
def add_hooks(self) -> list[Var | str]:
|
||||||
"""Add hooks for the toaster component.
|
"""Add hooks for the toaster component.
|
||||||
|
|
||||||
@ -241,13 +239,17 @@ class Toaster(Component):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def send_toast(
|
def send_toast(
|
||||||
message: str | Var = "", level: str | None = None, **props
|
message: str | Var = "",
|
||||||
|
level: str | None = None,
|
||||||
|
fallback_to_alert: bool = False,
|
||||||
|
**props,
|
||||||
) -> EventSpec:
|
) -> EventSpec:
|
||||||
"""Send a toast message.
|
"""Send a toast message.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message: The message to display.
|
message: The message to display.
|
||||||
level: The level of the toast.
|
level: The level of the toast.
|
||||||
|
fallback_to_alert: Whether to fallback to an alert if the toaster is not created.
|
||||||
**props: The options for the toast.
|
**props: The options for the toast.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
@ -256,11 +258,6 @@ class Toaster(Component):
|
|||||||
Returns:
|
Returns:
|
||||||
The toast event.
|
The toast event.
|
||||||
"""
|
"""
|
||||||
if not Toaster.is_used:
|
|
||||||
raise ValueError(
|
|
||||||
"Toaster component must be created before sending a toast. (use `rx.toast.provider()`)"
|
|
||||||
)
|
|
||||||
|
|
||||||
toast_command = (
|
toast_command = (
|
||||||
ObjectVar.__getattr__(toast_ref.to(dict), level) if level else toast_ref
|
ObjectVar.__getattr__(toast_ref.to(dict), level) if level else toast_ref
|
||||||
).to(FunctionVar)
|
).to(FunctionVar)
|
||||||
@ -277,6 +274,21 @@ class Toaster(Component):
|
|||||||
else:
|
else:
|
||||||
toast = toast_command.call(message)
|
toast = toast_command.call(message)
|
||||||
|
|
||||||
|
if fallback_to_alert:
|
||||||
|
toast = ternary_operation(
|
||||||
|
toast_ref.bool(),
|
||||||
|
toast,
|
||||||
|
FunctionVar("window.alert").call(
|
||||||
|
Var.create(
|
||||||
|
message
|
||||||
|
if isinstance(message, str) and message
|
||||||
|
else props.get("title", props.get("description", ""))
|
||||||
|
)
|
||||||
|
.to(str)
|
||||||
|
.replace("<br/>", "\n")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return run_script(toast)
|
return run_script(toast)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -379,7 +391,6 @@ class Toaster(Component):
|
|||||||
Returns:
|
Returns:
|
||||||
The toaster component.
|
The toaster component.
|
||||||
"""
|
"""
|
||||||
cls.is_used = True
|
|
||||||
return super().create(*children, **props)
|
return super().create(*children, **props)
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# ------------------- DO NOT EDIT ----------------------
|
# ------------------- DO NOT EDIT ----------------------
|
||||||
# This file was generated by `reflex/utils/pyi_generator.py`!
|
# This file was generated by `reflex/utils/pyi_generator.py`!
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
from typing import Any, ClassVar, Dict, Literal, Optional, Union, overload
|
from typing import Any, Dict, Literal, Optional, Union, overload
|
||||||
|
|
||||||
from reflex.base import Base
|
from reflex.base import Base
|
||||||
from reflex.components.component import Component, ComponentNamespace
|
from reflex.components.component import Component, ComponentNamespace
|
||||||
@ -60,12 +60,13 @@ class ToastProps(PropsBase, NoExtrasAllowedProps):
|
|||||||
def dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: ...
|
def dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: ...
|
||||||
|
|
||||||
class Toaster(Component):
|
class Toaster(Component):
|
||||||
is_used: ClassVar[bool] = False
|
|
||||||
|
|
||||||
def add_hooks(self) -> list[Var | str]: ...
|
def add_hooks(self) -> list[Var | str]: ...
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def send_toast(
|
def send_toast(
|
||||||
message: str | Var = "", level: str | None = None, **props
|
message: str | Var = "",
|
||||||
|
level: str | None = None,
|
||||||
|
fallback_to_alert: bool = False,
|
||||||
|
**props,
|
||||||
) -> EventSpec: ...
|
) -> EventSpec: ...
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def toast_info(message: str | Var = "", **kwargs: Any): ...
|
def toast_info(message: str | Var = "", **kwargs: Any): ...
|
||||||
@ -185,13 +186,17 @@ class ToastNamespace(ComponentNamespace):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __call__(
|
def __call__(
|
||||||
message: Union[str, Var] = "", level: Optional[str] = None, **props
|
message: Union[str, Var] = "",
|
||||||
|
level: Optional[str] = None,
|
||||||
|
fallback_to_alert: bool = False,
|
||||||
|
**props,
|
||||||
) -> "EventSpec":
|
) -> "EventSpec":
|
||||||
"""Send a toast message.
|
"""Send a toast message.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message: The message to display.
|
message: The message to display.
|
||||||
level: The level of the toast.
|
level: The level of the toast.
|
||||||
|
fallback_to_alert: Whether to fallback to an alert if the toaster is not created.
|
||||||
**props: The options for the toast.
|
**props: The options for the toast.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
@ -195,3 +195,7 @@ class PackageJson(SimpleNamespace):
|
|||||||
"postcss": "8.5.1",
|
"postcss": "8.5.1",
|
||||||
"postcss-import": "16.1.0",
|
"postcss-import": "16.1.0",
|
||||||
}
|
}
|
||||||
|
OVERRIDES = {
|
||||||
|
# This should always match the `react` version in DEPENDENCIES for recharts compatibility.
|
||||||
|
"react-is": "19.0.0"
|
||||||
|
}
|
||||||
|
@ -846,6 +846,7 @@ def _compile_package_json():
|
|||||||
},
|
},
|
||||||
dependencies=constants.PackageJson.DEPENDENCIES,
|
dependencies=constants.PackageJson.DEPENDENCIES,
|
||||||
dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
|
dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
|
||||||
|
overrides=constants.PackageJson.OVERRIDES,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ import reflex.config
|
|||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.app import App
|
from reflex.app import App
|
||||||
from reflex.base import Base
|
from reflex.base import Base
|
||||||
from reflex.components.sonner.toast import Toaster
|
|
||||||
from reflex.constants import CompileVars, RouteVar, SocketEvent
|
from reflex.constants import CompileVars, RouteVar, SocketEvent
|
||||||
from reflex.event import Event, EventHandler
|
from reflex.event import Event, EventHandler
|
||||||
from reflex.state import (
|
from reflex.state import (
|
||||||
@ -1613,29 +1612,20 @@ async def test_state_with_invalid_yield(capsys, mock_app):
|
|||||||
rx.event.Event(token="fake_token", name="invalid_handler")
|
rx.event.Event(token="fake_token", name="invalid_handler")
|
||||||
):
|
):
|
||||||
assert not update.delta
|
assert not update.delta
|
||||||
if Toaster.is_used:
|
assert update.events == rx.event.fix_events(
|
||||||
assert update.events == rx.event.fix_events(
|
[
|
||||||
[
|
rx.toast(
|
||||||
rx.toast(
|
"An error occurred.",
|
||||||
"An error occurred.",
|
level="error",
|
||||||
description="TypeError: Your handler test_state_with_invalid_yield.<locals>.StateWithInvalidYield.invalid_handler must only return/yield: None, Events or other EventHandlers referenced by their class (not using `self`).<br/>See logs for details.",
|
fallback_to_alert=True,
|
||||||
level="error",
|
description="TypeError: Your handler test_state_with_invalid_yield.<locals>.StateWithInvalidYield.invalid_handler must only return/yield: None, Events or other EventHandlers referenced by their class (not using `self`).<br/>See logs for details.",
|
||||||
id="backend_error",
|
id="backend_error",
|
||||||
position="top-center",
|
position="top-center",
|
||||||
style={"width": "500px"},
|
style={"width": "500px"},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
token="",
|
token="",
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
assert update.events == rx.event.fix_events(
|
|
||||||
[
|
|
||||||
rx.window_alert(
|
|
||||||
"An error occurred.\nContact the website administrator."
|
|
||||||
)
|
|
||||||
],
|
|
||||||
token="",
|
|
||||||
)
|
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "must only return/yield: None, Events or other EventHandlers" in captured.out
|
assert "must only return/yield: None, Events or other EventHandlers" in captured.out
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user