diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4d2e76b31..e2983d1d1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,7 +3,7 @@ fail_fast: true
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
- rev: v0.6.9
+ rev: v0.7.0
hooks:
- id: ruff-format
args: [reflex, tests]
diff --git a/poetry.lock b/poetry.lock
index 2f77234d4..7161e6af7 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -570,18 +570,18 @@ test = ["pytest (>=6)"]
[[package]]
name = "fastapi"
-version = "0.115.2"
+version = "0.115.3"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
files = [
- {file = "fastapi-0.115.2-py3-none-any.whl", hash = "sha256:61704c71286579cc5a598763905928f24ee98bfcc07aabe84cfefb98812bbc86"},
- {file = "fastapi-0.115.2.tar.gz", hash = "sha256:3995739e0b09fa12f984bce8fa9ae197b35d433750d3d312422d846e283697ee"},
+ {file = "fastapi-0.115.3-py3-none-any.whl", hash = "sha256:8035e8f9a2b0aa89cea03b6c77721178ed5358e1aea4cd8570d9466895c0638c"},
+ {file = "fastapi-0.115.3.tar.gz", hash = "sha256:c091c6a35599c036d676fa24bd4a6e19fa30058d93d950216cdc672881f6f7db"},
]
[package.dependencies]
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
-starlette = ">=0.37.2,<0.41.0"
+starlette = ">=0.40.0,<0.42.0"
typing-extensions = ">=4.8.0"
[package.extras]
@@ -970,13 +970,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
[[package]]
name = "mako"
-version = "1.3.5"
+version = "1.3.6"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
optional = false
python-versions = ">=3.8"
files = [
- {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"},
- {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"},
+ {file = "Mako-1.3.6-py3-none-any.whl", hash = "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a"},
+ {file = "mako-1.3.6.tar.gz", hash = "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d"},
]
[package.dependencies]
@@ -1977,6 +1977,20 @@ files = [
[package.dependencies]
six = ">=1.5"
+[[package]]
+name = "python-dotenv"
+version = "1.0.1"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
+ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
[[package]]
name = "python-engineio"
version = "4.10.1"
@@ -2253,13 +2267,13 @@ idna2008 = ["idna"]
[[package]]
name = "rich"
-version = "13.9.2"
+version = "13.9.3"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"},
- {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"},
+ {file = "rich-13.9.3-py3-none-any.whl", hash = "sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283"},
+ {file = "rich-13.9.3.tar.gz", hash = "sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e"},
]
[package.dependencies]
@@ -2272,29 +2286,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
-version = "0.6.9"
+version = "0.7.0"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"},
- {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"},
- {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"},
- {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"},
- {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"},
- {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"},
- {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"},
- {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"},
- {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"},
- {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"},
- {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"},
- {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"},
- {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"},
- {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"},
- {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"},
- {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"},
- {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"},
- {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"},
+ {file = "ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628"},
+ {file = "ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737"},
+ {file = "ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06"},
+ {file = "ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be"},
+ {file = "ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa"},
+ {file = "ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495"},
+ {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598"},
+ {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e"},
+ {file = "ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914"},
+ {file = "ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9"},
+ {file = "ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4"},
+ {file = "ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9"},
+ {file = "ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d"},
+ {file = "ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11"},
+ {file = "ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec"},
+ {file = "ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2"},
+ {file = "ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e"},
+ {file = "ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b"},
]
[[package]]
@@ -2525,13 +2539,13 @@ SQLAlchemy = ">=2.0.14,<2.1.0"
[[package]]
name = "starlette"
-version = "0.40.0"
+version = "0.41.0"
description = "The little ASGI library that shines."
optional = false
python-versions = ">=3.8"
files = [
- {file = "starlette-0.40.0-py3-none-any.whl", hash = "sha256:c494a22fae73805376ea6bf88439783ecfba9aac88a43911b48c653437e784c4"},
- {file = "starlette-0.40.0.tar.gz", hash = "sha256:1a3139688fb298ce5e2d661d37046a66ad996ce94be4d4983be019a23a04ea35"},
+ {file = "starlette-0.41.0-py3-none-any.whl", hash = "sha256:a0193a3c413ebc9c78bff1c3546a45bb8c8bcb4a84cae8747d650a65bd37210a"},
+ {file = "starlette-0.41.0.tar.gz", hash = "sha256:39cbd8768b107d68bfe1ff1672b38a2c38b49777de46d2a592841d58e3bf7c2a"},
]
[package.dependencies]
@@ -3033,4 +3047,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
-content-hash = "edb7145394dd61f5a665b5519cc0c091c8c1628200ea1170857cff1a6bdb829e"
+content-hash = "c5da15520cef58124f6699007c81158036840469d4f9972592d72bd456c45e7e"
diff --git a/pyproject.toml b/pyproject.toml
index d4f583189..3fe6041f0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -33,6 +33,7 @@ jinja2 = ">=3.1.2,<4.0"
psutil = ">=5.9.4,<7.0"
pydantic = ">=1.10.2,<3.0"
python-multipart = ">=0.0.5,<0.1"
+python-dotenv = ">=1.0.1"
python-socketio = ">=5.7.0,<6.0"
redis = ">=4.3.5,<6.0"
rich = ">=13.0.0,<14.0"
@@ -68,7 +69,7 @@ darglint = ">=1.8.1,<2.0"
toml = ">=0.10.2,<1.0"
pytest-asyncio = ">=0.24.0"
pytest-cov = ">=4.0.0,<6.0"
-ruff = "^0.6.9"
+ruff = "^0.7.0"
pandas = ">=2.1.1,<3.0"
pillow = ">=10.0.0,<12.0"
plotly = ">=5.13.0,<6.0"
@@ -91,7 +92,7 @@ build-backend = "poetry.core.masonry.api"
[tool.ruff]
target-version = "py39"
lint.select = ["B", "D", "E", "F", "I", "SIM", "W"]
-lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541"]
+lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541", "SIM115"]
lint.pydocstyle.convention = "google"
[tool.ruff.lint.per-file-ignores]
diff --git a/reflex/.templates/web/components/shiki/code.js b/reflex/.templates/web/components/shiki/code.js
new file mode 100644
index 000000000..c655c3a60
--- /dev/null
+++ b/reflex/.templates/web/components/shiki/code.js
@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react"
+import { codeToHtml} from "shiki"
+
+export function Code ({code, theme, language, transformers, ...divProps}) {
+ const [codeResult, setCodeResult] = useState("")
+ useEffect(() => {
+ async function fetchCode() {
+ let final_code;
+
+ if (Array.isArray(code)) {
+ final_code = code[0];
+ } else {
+ final_code = code;
+ }
+ const result = await codeToHtml(final_code, {
+ lang: language,
+ theme,
+ transformers
+ });
+ setCodeResult(result);
+ }
+ fetchCode();
+ }, [code, language, theme, transformers]
+
+ )
+ return (
+
+ )
+}
diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js
index 0fe0db8c1..24092f235 100644
--- a/reflex/.templates/web/utils/state.js
+++ b/reflex/.templates/web/utils/state.js
@@ -743,6 +743,7 @@ export const useEventLoop = (
addEvents([
Event(`${exception_state_name}.handle_frontend_exception`, {
stack: error.stack,
+ component_stack: "",
}),
]);
return false;
@@ -754,6 +755,7 @@ export const useEventLoop = (
addEvents([
Event(`${exception_state_name}.handle_frontend_exception`, {
stack: event.reason.stack,
+ component_stack: "",
}),
]);
return false;
diff --git a/reflex/__init__.py b/reflex/__init__.py
index ad51d2cf4..ffc4426f9 100644
--- a/reflex/__init__.py
+++ b/reflex/__init__.py
@@ -320,15 +320,18 @@ _MAPPING: dict = {
"upload_files",
"window_alert",
],
+ "istate.storage": [
+ "Cookie",
+ "LocalStorage",
+ "SessionStorage",
+ ],
"middleware": ["middleware", "Middleware"],
"model": ["session", "Model"],
"state": [
"var",
- "Cookie",
- "LocalStorage",
- "SessionStorage",
"ComponentState",
"State",
+ "dynamic",
],
"style": ["Style", "toggle_color_mode"],
"utils.imports": ["ImportVar"],
diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi
index d928778d8..aa1c92b72 100644
--- a/reflex/__init__.pyi
+++ b/reflex/__init__.pyi
@@ -174,16 +174,17 @@ from .event import stop_propagation as stop_propagation
from .event import upload_files as upload_files
from .event import window_alert as window_alert
from .experimental import _x as _x
+from .istate.storage import Cookie as Cookie
+from .istate.storage import LocalStorage as LocalStorage
+from .istate.storage import SessionStorage as SessionStorage
from .middleware import Middleware as Middleware
from .middleware import middleware as middleware
from .model import Model as Model
from .model import session as session
from .page import page as page
from .state import ComponentState as ComponentState
-from .state import Cookie as Cookie
-from .state import LocalStorage as LocalStorage
-from .state import SessionStorage as SessionStorage
from .state import State as State
+from .state import dynamic as dynamic
from .state import var as var
from .style import Style as Style
from .style import toggle_color_mode as toggle_color_mode
diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py
index 6f4fa2d1b..40640faa6 100644
--- a/reflex/compiler/utils.py
+++ b/reflex/compiler/utils.py
@@ -28,7 +28,8 @@ from reflex.components.base import (
Title,
)
from reflex.components.component import Component, ComponentStyle, CustomComponent
-from reflex.state import BaseState, Cookie, LocalStorage, SessionStorage
+from reflex.istate.storage import Cookie, LocalStorage, SessionStorage
+from reflex.state import BaseState
from reflex.style import Style
from reflex.utils import console, format, imports, path_ops
from reflex.utils.imports import ImportVar, ParsedImportDict
diff --git a/reflex/components/base/error_boundary.py b/reflex/components/base/error_boundary.py
index 66a9c43c8..83becc034 100644
--- a/reflex/components/base/error_boundary.py
+++ b/reflex/components/base/error_boundary.py
@@ -2,16 +2,32 @@
from __future__ import annotations
-from typing import List
+from typing import Dict, List, Tuple
from reflex.compiler.compiler import _compile_component
from reflex.components.component import Component
from reflex.components.el import div, p
-from reflex.constants import Hooks, Imports
-from reflex.event import EventChain, EventHandler
-from reflex.utils.imports import ImportVar
+from reflex.event import EventHandler
+from reflex.state import FrontendEventExceptionState
from reflex.vars.base import Var
-from reflex.vars.function import FunctionVar
+
+
+def on_error_spec(
+ error: Var[Dict[str, str]], info: Var[Dict[str, str]]
+) -> Tuple[Var[str], Var[str]]:
+ """The spec for the on_error event handler.
+
+ Args:
+ error: The error message.
+ info: Additional information about the error.
+
+ Returns:
+ The arguments for the event handler.
+ """
+ return (
+ error.stack,
+ info.componentStack,
+ )
class ErrorBoundary(Component):
@@ -21,31 +37,13 @@ class ErrorBoundary(Component):
tag = "ErrorBoundary"
# Fired when the boundary catches an error.
- on_error: EventHandler[lambda error, info: [error, info]] = Var( # type: ignore
- "logFrontendError"
- ).to(FunctionVar, EventChain)
+ on_error: EventHandler[on_error_spec]
# Rendered instead of the children when an error is caught.
Fallback_component: Var[Component] = Var(_js_expr="Fallback")._replace(
_var_type=Component
)
- def add_imports(self) -> dict[str, list[ImportVar]]:
- """Add imports for the component.
-
- Returns:
- The imports to add.
- """
- return Imports.EVENTS
-
- def add_hooks(self) -> List[str | Var]:
- """Add hooks for the component.
-
- Returns:
- The hooks to add.
- """
- return [Hooks.EVENTS, Hooks.FRONTEND_ERRORS]
-
def add_custom_code(self) -> List[str]:
"""Add custom Javascript code into the page that contains this component.
@@ -75,5 +73,20 @@ class ErrorBoundary(Component):
"""
]
+ @classmethod
+ def create(cls, *children, **props):
+ """Create an ErrorBoundary component.
+
+ Args:
+ *children: The children of the component.
+ **props: The props of the component.
+
+ Returns:
+ The ErrorBoundary component.
+ """
+ if "on_error" not in props:
+ props["on_error"] = FrontendEventExceptionState.handle_frontend_exception
+ return super().create(*children, **props)
+
error_boundary = ErrorBoundary.create
diff --git a/reflex/components/base/error_boundary.pyi b/reflex/components/base/error_boundary.pyi
index aaf5584e4..c84742851 100644
--- a/reflex/components/base/error_boundary.pyi
+++ b/reflex/components/base/error_boundary.pyi
@@ -3,17 +3,18 @@
# ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
-from typing import Any, Dict, List, Optional, Union, overload
+from typing import Any, Dict, List, Optional, Tuple, Union, overload
from reflex.components.component import Component
from reflex.event import EventType
from reflex.style import Style
-from reflex.utils.imports import ImportVar
from reflex.vars.base import Var
+def on_error_spec(
+ error: Var[Dict[str, str]], info: Var[Dict[str, str]]
+) -> Tuple[Var[str], Var[str]]: ...
+
class ErrorBoundary(Component):
- def add_imports(self) -> dict[str, list[ImportVar]]: ...
- def add_hooks(self) -> List[str | Var]: ...
def add_custom_code(self) -> List[str]: ...
@overload
@classmethod
@@ -31,7 +32,7 @@ class ErrorBoundary(Component):
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
- on_error: Optional[EventType] = None,
+ on_error: Optional[EventType[str, str]] = None,
on_focus: Optional[EventType[[]]] = None,
on_mount: Optional[EventType[[]]] = None,
on_mouse_down: Optional[EventType[[]]] = None,
@@ -45,7 +46,7 @@ class ErrorBoundary(Component):
on_unmount: Optional[EventType[[]]] = None,
**props,
) -> "ErrorBoundary":
- """Create the component.
+ """Create an ErrorBoundary component.
Args:
*children: The children of the component.
@@ -59,7 +60,7 @@ class ErrorBoundary(Component):
**props: The props of the component.
Returns:
- The component.
+ The ErrorBoundary component.
"""
...
diff --git a/reflex/components/core/clipboard.pyi b/reflex/components/core/clipboard.pyi
index e2f6afc8d..489a9bcc5 100644
--- a/reflex/components/core/clipboard.pyi
+++ b/reflex/components/core/clipboard.pyi
@@ -40,7 +40,7 @@ class Clipboard(Fragment):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_paste: Optional[EventType] = None,
+ on_paste: Optional[EventType[list[tuple[str, str]]]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/datadisplay/dataeditor.py b/reflex/components/datadisplay/dataeditor.py
index 01255dd14..b9bd4cebf 100644
--- a/reflex/components/datadisplay/dataeditor.py
+++ b/reflex/components/datadisplay/dataeditor.py
@@ -5,6 +5,8 @@ from __future__ import annotations
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
+from typing_extensions import TypedDict
+
from reflex.base import Base
from reflex.components.component import Component, NoSSRComponent
from reflex.components.literals import LiteralRowMarker
@@ -107,17 +109,76 @@ class DataEditorTheme(Base):
text_medium: Optional[str] = None
-def on_edit_spec(pos, data: dict[str, Any]):
- """The on edit spec function.
+class Bounds(TypedDict):
+ """The bounds of the group header."""
- Args:
- pos: The position of the edit event.
- data: The data of the edit event.
+ x: int
+ y: int
+ width: int
+ height: int
- Returns:
- The position and data.
- """
- return [pos, data]
+
+class CompatSelection(TypedDict):
+ """The selection."""
+
+ items: list
+
+
+class Rectangle(TypedDict):
+ """The bounds of the group header."""
+
+ x: int
+ y: int
+ width: int
+ height: int
+
+
+class GridSelectionCurrent(TypedDict):
+ """The current selection."""
+
+ cell: tuple[int, int]
+ range: Rectangle
+ rangeStack: list[Rectangle]
+
+
+class GridSelection(TypedDict):
+ """The grid selection."""
+
+ current: Optional[GridSelectionCurrent]
+ columns: CompatSelection
+ rows: CompatSelection
+
+
+class GroupHeaderClickedEventArgs(TypedDict):
+ """The arguments for the group header clicked event."""
+
+ kind: str
+ group: str
+ location: tuple[int, int]
+ bounds: Bounds
+ isEdge: bool
+ shiftKey: bool
+ ctrlKey: bool
+ metaKey: bool
+ isTouch: bool
+ localEventX: int
+ localEventY: int
+ button: int
+ buttons: int
+ scrollEdge: tuple[int, int]
+
+
+class GridCell(TypedDict):
+ """The grid cell."""
+
+ span: Optional[List[int]]
+
+
+class GridColumn(TypedDict):
+ """The grid column."""
+
+ title: str
+ group: Optional[str]
class DataEditor(NoSSRComponent):
@@ -232,16 +293,18 @@ class DataEditor(NoSSRComponent):
on_cell_context_menu: EventHandler[identity_event(Tuple[int, int])]
# Fired when a cell is edited.
- on_cell_edited: EventHandler[on_edit_spec]
+ on_cell_edited: EventHandler[identity_event(Tuple[int, int], GridCell)]
# Fired when a group header is clicked.
- on_group_header_clicked: EventHandler[on_edit_spec]
+ on_group_header_clicked: EventHandler[identity_event(Tuple[int, int], GridCell)]
# Fired when a group header is right-clicked.
- on_group_header_context_menu: EventHandler[lambda grp_idx, data: [grp_idx, data]]
+ on_group_header_context_menu: EventHandler[
+ identity_event(int, GroupHeaderClickedEventArgs)
+ ]
# Fired when a group header is renamed.
- on_group_header_renamed: EventHandler[lambda idx, val: [idx, val]]
+ on_group_header_renamed: EventHandler[identity_event(str, str)]
# Fired when a header is clicked.
on_header_clicked: EventHandler[identity_event(Tuple[int, int])]
@@ -250,16 +313,18 @@ class DataEditor(NoSSRComponent):
on_header_context_menu: EventHandler[identity_event(Tuple[int, int])]
# Fired when a header menu item is clicked.
- on_header_menu_click: EventHandler[lambda col, pos: [col, pos]]
+ on_header_menu_click: EventHandler[identity_event(int, Rectangle)]
# Fired when an item is hovered.
on_item_hovered: EventHandler[identity_event(Tuple[int, int])]
# Fired when a selection is deleted.
- on_delete: EventHandler[lambda selection: [selection]]
+ on_delete: EventHandler[identity_event(GridSelection)]
# Fired when editing is finished.
- on_finished_editing: EventHandler[lambda new_value, movement: [new_value, movement]]
+ on_finished_editing: EventHandler[
+ identity_event(Union[GridCell, None], tuple[int, int])
+ ]
# Fired when a row is appended.
on_row_appended: EventHandler[empty_event]
@@ -268,7 +333,7 @@ class DataEditor(NoSSRComponent):
on_selection_cleared: EventHandler[empty_event]
# Fired when a column is resized.
- on_column_resize: EventHandler[lambda col, width: [col, width]]
+ on_column_resize: EventHandler[identity_event(GridColumn, int)]
def add_imports(self) -> ImportDict:
"""Add imports for the component.
diff --git a/reflex/components/datadisplay/dataeditor.pyi b/reflex/components/datadisplay/dataeditor.pyi
index 1b8fed287..6402aaf42 100644
--- a/reflex/components/datadisplay/dataeditor.pyi
+++ b/reflex/components/datadisplay/dataeditor.pyi
@@ -6,6 +6,8 @@
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Union, overload
+from typing_extensions import TypedDict
+
from reflex.base import Base
from reflex.components.component import NoSSRComponent
from reflex.event import EventType
@@ -76,7 +78,53 @@ class DataEditorTheme(Base):
text_light: Optional[str]
text_medium: Optional[str]
-def on_edit_spec(pos, data: dict[str, Any]): ...
+class Bounds(TypedDict):
+ x: int
+ y: int
+ width: int
+ height: int
+
+class CompatSelection(TypedDict):
+ items: list
+
+class Rectangle(TypedDict):
+ x: int
+ y: int
+ width: int
+ height: int
+
+class GridSelectionCurrent(TypedDict):
+ cell: tuple[int, int]
+ range: Rectangle
+ rangeStack: list[Rectangle]
+
+class GridSelection(TypedDict):
+ current: Optional[GridSelectionCurrent]
+ columns: CompatSelection
+ rows: CompatSelection
+
+class GroupHeaderClickedEventArgs(TypedDict):
+ kind: str
+ group: str
+ location: tuple[int, int]
+ bounds: Bounds
+ isEdge: bool
+ shiftKey: bool
+ ctrlKey: bool
+ metaKey: bool
+ isTouch: bool
+ localEventX: int
+ localEventY: int
+ button: int
+ buttons: int
+ scrollEdge: tuple[int, int]
+
+class GridCell(TypedDict):
+ span: Optional[List[int]]
+
+class GridColumn(TypedDict):
+ title: str
+ group: Optional[str]
class DataEditor(NoSSRComponent):
def add_imports(self) -> ImportDict: ...
@@ -136,24 +184,28 @@ class DataEditor(NoSSRComponent):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_cell_activated: Optional[EventType] = None,
- on_cell_clicked: Optional[EventType] = None,
- on_cell_context_menu: Optional[EventType] = None,
- on_cell_edited: Optional[EventType] = None,
+ on_cell_activated: Optional[EventType[tuple[int, int]]] = None,
+ on_cell_clicked: Optional[EventType[tuple[int, int]]] = None,
+ on_cell_context_menu: Optional[EventType[tuple[int, int]]] = None,
+ on_cell_edited: Optional[EventType[tuple[int, int], GridCell]] = None,
on_click: Optional[EventType[[]]] = None,
- on_column_resize: Optional[EventType] = None,
+ on_column_resize: Optional[EventType[GridColumn, int]] = None,
on_context_menu: Optional[EventType[[]]] = None,
- on_delete: Optional[EventType] = None,
+ on_delete: Optional[EventType[GridSelection]] = None,
on_double_click: Optional[EventType[[]]] = None,
- on_finished_editing: Optional[EventType] = None,
+ on_finished_editing: Optional[
+ EventType[Union[GridCell, None], tuple[int, int]]
+ ] = None,
on_focus: Optional[EventType[[]]] = None,
- on_group_header_clicked: Optional[EventType] = None,
- on_group_header_context_menu: Optional[EventType] = None,
- on_group_header_renamed: Optional[EventType] = None,
- on_header_clicked: Optional[EventType] = None,
- on_header_context_menu: Optional[EventType] = None,
- on_header_menu_click: Optional[EventType] = None,
- on_item_hovered: Optional[EventType] = None,
+ on_group_header_clicked: Optional[EventType[tuple[int, int], GridCell]] = None,
+ on_group_header_context_menu: Optional[
+ EventType[int, GroupHeaderClickedEventArgs]
+ ] = None,
+ on_group_header_renamed: Optional[EventType[str, str]] = None,
+ on_header_clicked: Optional[EventType[tuple[int, int]]] = None,
+ on_header_context_menu: Optional[EventType[tuple[int, int]]] = None,
+ on_header_menu_click: Optional[EventType[int, Rectangle]] = None,
+ on_item_hovered: Optional[EventType[tuple[int, int]]] = None,
on_mount: Optional[EventType[[]]] = None,
on_mouse_down: Optional[EventType[[]]] = None,
on_mouse_enter: Optional[EventType[[]]] = None,
diff --git a/reflex/components/datadisplay/shiki_code_block.py b/reflex/components/datadisplay/shiki_code_block.py
new file mode 100644
index 000000000..46199a6e4
--- /dev/null
+++ b/reflex/components/datadisplay/shiki_code_block.py
@@ -0,0 +1,813 @@
+"""Shiki syntax hghlighter component."""
+
+from __future__ import annotations
+
+import re
+from collections import defaultdict
+from typing import Any, Literal, Optional, Union
+
+from reflex.base import Base
+from reflex.components.component import Component, ComponentNamespace
+from reflex.components.core.colors import color
+from reflex.components.core.cond import color_mode_cond
+from reflex.components.el.elements.forms import Button
+from reflex.components.lucide.icon import Icon
+from reflex.components.radix.themes.layout.box import Box
+from reflex.event import call_script, set_clipboard
+from reflex.style import Style
+from reflex.utils.exceptions import VarTypeError
+from reflex.utils.imports import ImportVar
+from reflex.vars.base import LiteralVar, Var
+from reflex.vars.function import FunctionStringVar
+from reflex.vars.sequence import StringVar, string_replace_operation
+
+
+def copy_script() -> Any:
+ """Copy script for the code block and modify the child SVG element.
+
+
+ Returns:
+ Any: The result of calling the script.
+ """
+ return call_script(
+ f"""
+// Event listener for the parent click
+document.addEventListener('click', function(event) {{
+ // Find the closest div (parent element)
+ const parent = event.target.closest('div');
+ // If the parent is found
+ if (parent) {{
+ // Find the SVG element within the parent
+ const svgIcon = parent.querySelector('svg');
+ // If the SVG exists, proceed with the script
+ if (svgIcon) {{
+ const originalPath = svgIcon.innerHTML;
+ const checkmarkPath = ''; // Checkmark SVG path
+ function transition(element, scale, opacity) {{
+ element.style.transform = `scale(${{scale}})`;
+ element.style.opacity = opacity;
+ }}
+ // Animate the SVG
+ transition(svgIcon, 0, '0');
+ setTimeout(() => {{
+ svgIcon.innerHTML = checkmarkPath; // Replace content with checkmark
+ svgIcon.setAttribute('viewBox', '0 0 24 24'); // Adjust viewBox if necessary
+ transition(svgIcon, 1, '1');
+ setTimeout(() => {{
+ transition(svgIcon, 0, '0');
+ setTimeout(() => {{
+ svgIcon.innerHTML = originalPath; // Restore original SVG content
+ transition(svgIcon, 1, '1');
+ }}, 125);
+ }}, 600);
+ }}, 125);
+ }} else {{
+ // console.error('SVG element not found within the parent.');
+ }}
+ }} else {{
+ // console.error('Parent element not found.');
+ }}
+}});
+"""
+ )
+
+
+SHIKIJS_TRANSFORMER_FNS = {
+ "transformerNotationDiff",
+ "transformerNotationHighlight",
+ "transformerNotationWordHighlight",
+ "transformerNotationFocus",
+ "transformerNotationErrorLevel",
+ "transformerRenderWhitespace",
+ "transformerMetaHighlight",
+ "transformerMetaWordHighlight",
+ "transformerCompactLineOptions",
+ # TODO: this transformer when included adds a weird behavior which removes other code lines. Need to figure out why.
+ # "transformerRemoveLineBreak",
+ "transformerRemoveNotationEscape",
+}
+LINE_NUMBER_STYLING = {
+ "code": {
+ "counter-reset": "step",
+ "counter-increment": "step 0",
+ "display": "grid",
+ "line-height": "1.7",
+ "font-size": "0.875em",
+ },
+ "code .line::before": {
+ "content": "counter(step)",
+ "counter-increment": "step",
+ "width": "1rem",
+ "margin-right": "1.5rem",
+ "display": "inline-block",
+ "text-align": "right",
+ "color": "rgba(115,138,148,.4)",
+ },
+}
+BOX_PARENT_STYLING = {
+ "pre": {
+ "margin": "0",
+ "padding": "24px",
+ "background": "transparent",
+ "overflow-x": "auto",
+ "border-radius": "6px",
+ },
+}
+
+THEME_MAPPING = {
+ "light": "one-light",
+ "dark": "one-dark-pro",
+ "a11y-dark": "github-dark",
+}
+LANGUAGE_MAPPING = {"bash": "shellscript"}
+LiteralCodeLanguage = Literal[
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "coq",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+]
+LiteralCodeTheme = Literal[
+ "andromeeda",
+ "aurora-x",
+ "ayu-dark",
+ "catppuccin-frappe",
+ "catppuccin-latte",
+ "catppuccin-macchiato",
+ "catppuccin-mocha",
+ "dark-plus",
+ "dracula",
+ "dracula-soft",
+ "everforest-dark",
+ "everforest-light",
+ "github-dark",
+ "github-dark-default",
+ "github-dark-dimmed",
+ "github-dark-high-contrast",
+ "github-light",
+ "github-light-default",
+ "github-light-high-contrast",
+ "houston",
+ "laserwave",
+ "light-plus",
+ "material-theme",
+ "material-theme-darker",
+ "material-theme-lighter",
+ "material-theme-ocean",
+ "material-theme-palenight",
+ "min-dark",
+ "min-light",
+ "monokai",
+ "night-owl",
+ "nord",
+ "one-dark-pro",
+ "one-light",
+ "plain",
+ "plastic",
+ "poimandres",
+ "red",
+ "rose-pine",
+ "rose-pine-dawn",
+ "rose-pine-moon",
+ "slack-dark",
+ "slack-ochin",
+ "snazzy-light",
+ "solarized-dark",
+ "solarized-light",
+ "synthwave-84",
+ "tokyo-night",
+ "vesper",
+ "vitesse-black",
+ "vitesse-dark",
+ "vitesse-light",
+]
+
+
+class ShikiBaseTransformers(Base):
+ """Base for creating transformers."""
+
+ library: str
+ fns: list[FunctionStringVar]
+ style: Optional[Style]
+
+
+class ShikiJsTransformer(ShikiBaseTransformers):
+ """A Wrapped shikijs transformer."""
+
+ library: str = "@shikijs/transformers"
+ fns: list[FunctionStringVar] = [
+ FunctionStringVar.create(fn) for fn in SHIKIJS_TRANSFORMER_FNS
+ ]
+ style: Optional[Style] = Style(
+ {
+ "code": {"line-height": "1.7", "font-size": "0.875em", "display": "grid"},
+ # Diffs
+ ".diff": {
+ "margin": "0 -24px",
+ "padding": "0 24px",
+ "width": "calc(100% + 48px)",
+ "display": "inline-block",
+ },
+ ".diff.add": {
+ "background-color": "rgba(16, 185, 129, .14)",
+ "position": "relative",
+ },
+ ".diff.remove": {
+ "background-color": "rgba(244, 63, 94, .14)",
+ "opacity": "0.7",
+ "position": "relative",
+ },
+ ".diff.remove:after": {
+ "position": "absolute",
+ "left": "10px",
+ "content": "'-'",
+ "color": "#b34e52",
+ },
+ ".diff.add:after": {
+ "position": "absolute",
+ "left": "10px",
+ "content": "'+'",
+ "color": "#18794e",
+ },
+ # Highlight
+ ".highlighted": {
+ "background-color": "rgba(142, 150, 170, .14)",
+ "margin": "0 -24px",
+ "padding": "0 24px",
+ "width": "calc(100% + 48px)",
+ "display": "inline-block",
+ },
+ ".highlighted.error": {
+ "background-color": "rgba(244, 63, 94, .14)",
+ },
+ ".highlighted.warning": {
+ "background-color": "rgba(234, 179, 8, .14)",
+ },
+ # Highlighted Word
+ ".highlighted-word": {
+ "background-color": color("gray", 2),
+ "border": f"1px solid {color('gray', 5)}",
+ "padding": "1px 3px",
+ "margin": "-1px -3px",
+ "border-radius": "4px",
+ },
+ # Focused Lines
+ ".has-focused .line:not(.focused)": {
+ "opacity": "0.7",
+ "filter": "blur(0.095rem)",
+ "transition": "filter .35s, opacity .35s",
+ },
+ ".has-focused:hover .line:not(.focused)": {
+ "opacity": "1",
+ "filter": "none",
+ },
+ # White Space
+ # ".tab, .space": {
+ # "position": "relative",
+ # },
+ # ".tab::before": {
+ # "content": "'⇥'",
+ # "position": "absolute",
+ # "opacity": "0.3",
+ # },
+ # ".space::before": {
+ # "content": "'·'",
+ # "position": "absolute",
+ # "opacity": "0.3",
+ # },
+ }
+ )
+
+ def __init__(self, **kwargs):
+ """Initialize the transformer.
+
+ Args:
+ kwargs: Kwargs to initialize the props.
+
+ """
+ fns = kwargs.pop("fns", None)
+ style = kwargs.pop("style", None)
+ if fns:
+ kwargs["fns"] = [
+ (
+ FunctionStringVar.create(x)
+ if not isinstance(x, FunctionStringVar)
+ else x
+ )
+ for x in fns
+ ]
+
+ if style:
+ kwargs["style"] = Style(style)
+ super().__init__(**kwargs)
+
+
+class ShikiCodeBlock(Component):
+ """A Code block."""
+
+ library = "/components/shiki/code"
+
+ tag = "Code"
+
+ alias = "ShikiCode"
+
+ lib_dependencies: list[str] = ["shiki"]
+
+ # The language to use.
+ language: Var[LiteralCodeLanguage] = Var.create("python")
+
+ # The theme to use ("light" or "dark").
+ theme: Var[LiteralCodeTheme] = Var.create("one-light")
+
+ # The set of themes to use for different modes.
+ themes: Var[Union[list[dict[str, Any]], dict[str, str]]]
+
+ # The code to display.
+ code: Var[str]
+
+ # The transformers to use for the syntax highlighter.
+ transformers: Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]] = Var.create(
+ []
+ )
+
+ @classmethod
+ def create(
+ cls,
+ *children,
+ **props,
+ ) -> Component:
+ """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
+
+ Args:
+ *children: The children of the component.
+ **props: The props to pass to the component.
+
+ Returns:
+ The code block component.
+ """
+ # Separate props for the code block and the wrapper
+ code_block_props = {}
+ code_wrapper_props = {}
+
+ class_props = cls.get_props()
+
+ # Distribute props between the code block and wrapper
+ for key, value in props.items():
+ (code_block_props if key in class_props else code_wrapper_props)[key] = (
+ value
+ )
+
+ code_block_props["code"] = children[0]
+ code_block = super().create(**code_block_props)
+
+ transformer_styles = {}
+ # Collect styles from transformers and wrapper
+ for transformer in code_block.transformers._var_value: # type: ignore
+ if isinstance(transformer, ShikiBaseTransformers) and transformer.style:
+ transformer_styles.update(transformer.style)
+ transformer_styles.update(code_wrapper_props.pop("style", {}))
+
+ return Box.create(
+ code_block,
+ *children[1:],
+ style=Style({**transformer_styles, **BOX_PARENT_STYLING}),
+ **code_wrapper_props,
+ )
+
+ def add_imports(self) -> dict[str, list[str]]:
+ """Add the necessary imports.
+ We add all referenced transformer functions as imports from their corresponding
+ libraries.
+
+ Returns:
+ Imports for the component.
+ """
+ imports = defaultdict(list)
+ for transformer in self.transformers._var_value:
+ if isinstance(transformer, ShikiBaseTransformers):
+ imports[transformer.library].extend(
+ [ImportVar(tag=str(fn)) for fn in transformer.fns]
+ )
+ (
+ self.lib_dependencies.append(transformer.library)
+ if transformer.library not in self.lib_dependencies
+ else None
+ )
+ return imports
+
+ @classmethod
+ def create_transformer(cls, library: str, fns: list[str]) -> ShikiBaseTransformers:
+ """Create a transformer from a third party library.
+
+ Args:
+ library: The name of the library.
+ fns: The str names of the functions/callables to invoke from the library.
+
+ Returns:
+ A transformer for the specified library.
+
+ Raises:
+ ValueError: If a supplied function name is not valid str.
+ """
+ if any(not isinstance(fn_name, str) for fn_name in fns):
+ raise ValueError(
+ f"the function names should be str names of functions in the specified transformer: {library!r}"
+ )
+ return ShikiBaseTransformers( # type: ignore
+ library=library, fns=[FunctionStringVar.create(fn) for fn in fns]
+ )
+
+ def _render(self, props: dict[str, Any] | None = None):
+ """Renders the component with the given properties, processing transformers if present.
+
+ Args:
+ props: Optional properties to pass to the render function.
+
+ Returns:
+ Rendered component output.
+ """
+ # Ensure props is initialized from class attributes if not provided
+ props = props or {
+ attr.rstrip("_"): getattr(self, attr) for attr in self.get_props()
+ }
+
+ # Extract transformers and apply transformations
+ transformers = props.get("transformers")
+ if transformers is not None:
+ transformed_values = self._process_transformers(transformers._var_value)
+ props["transformers"] = LiteralVar.create(transformed_values)
+
+ return super()._render(props)
+
+ def _process_transformers(self, transformer_list: list) -> list:
+ """Processes a list of transformers, applying transformations where necessary.
+
+ Args:
+ transformer_list: List of transformer objects or values.
+
+ Returns:
+ list: A list of transformed values.
+ """
+ processed = []
+
+ for transformer in transformer_list:
+ if isinstance(transformer, ShikiBaseTransformers):
+ processed.extend(fn.call() for fn in transformer.fns)
+ else:
+ processed.append(transformer)
+
+ return processed
+
+
+class ShikiHighLevelCodeBlock(ShikiCodeBlock):
+ """High level component for the shiki syntax highlighter."""
+
+ # If this is enabled, the default transformers(shikijs transformer) will be used.
+ use_transformers: Var[bool]
+
+ # If this is enabled line numbers will be shown next to the code block.
+ show_line_numbers: Var[bool]
+
+ # Whether a copy button should appear.
+ can_copy: Var[bool] = Var.create(False)
+
+ # copy_button: A custom copy button to override the default one.
+ copy_button: Var[Optional[Union[Component, bool]]] = Var.create(None)
+
+ @classmethod
+ def create(
+ cls,
+ *children,
+ **props,
+ ) -> Component:
+ """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
+
+ Args:
+ *children: The children of the component.
+ **props: The props to pass to the component.
+
+ Returns:
+ The code block component.
+ """
+ use_transformers = props.pop("use_transformers", False)
+ show_line_numbers = props.pop("show_line_numbers", False)
+ language = props.pop("language", None)
+ can_copy = props.pop("can_copy", False)
+ copy_button = props.pop("copy_button", None)
+
+ if use_transformers:
+ props["transformers"] = [ShikiJsTransformer()]
+
+ if language is not None:
+ props["language"] = cls._map_languages(language)
+
+ # line numbers are generated via css
+ if show_line_numbers:
+ props["style"] = {**LINE_NUMBER_STYLING, **props.get("style", {})}
+
+ theme = props.pop("theme", None)
+ props["theme"] = props["theme"] = (
+ cls._map_themes(theme)
+ if theme is not None
+ else color_mode_cond( # Default color scheme responds to global color mode.
+ light="one-light",
+ dark="one-dark-pro",
+ )
+ )
+
+ if can_copy:
+ code = children[0]
+ copy_button = ( # type: ignore
+ copy_button
+ if copy_button is not None
+ else Button.create(
+ Icon.create(tag="copy", size=16, color=color("gray", 11)),
+ on_click=[
+ set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore
+ copy_script(),
+ ],
+ style=Style(
+ {
+ "position": "absolute",
+ "top": "4px",
+ "right": "4px",
+ "background": color("gray", 3),
+ "border": "1px solid",
+ "border-color": color("gray", 5),
+ "border-radius": "6px",
+ "padding": "5px",
+ "opacity": "1",
+ "cursor": "pointer",
+ "_hover": {
+ "background": color("gray", 4),
+ },
+ "transition": "background 0.250s ease-out",
+ "&>svg": {
+ "transition": "transform 0.250s ease-out, opacity 0.250s ease-out",
+ },
+ "_active": {
+ "background": color("gray", 5),
+ },
+ }
+ ),
+ )
+ )
+
+ if copy_button:
+ return ShikiCodeBlock.create(
+ children[0], copy_button, position="relative", **props
+ )
+ else:
+ return ShikiCodeBlock.create(children[0], **props)
+
+ @staticmethod
+ def _map_themes(theme: str) -> str:
+ if isinstance(theme, str) and theme in THEME_MAPPING:
+ return THEME_MAPPING[theme]
+ return theme
+
+ @staticmethod
+ def _map_languages(language: str) -> str:
+ if isinstance(language, str) and language in LANGUAGE_MAPPING:
+ return LANGUAGE_MAPPING[language]
+ return language
+
+ @staticmethod
+ def _strip_transformer_triggers(code: str | StringVar) -> StringVar | str:
+ if not isinstance(code, (StringVar, str)):
+ raise VarTypeError(
+ f"code should be string literal or a StringVar type. Got {type(code)} instead."
+ )
+ regex_pattern = r"[\/#]+ *\[!code.*?\]"
+
+ if isinstance(code, Var):
+ return string_replace_operation(
+ code, StringVar(_js_expr=f"/{regex_pattern}/g", _var_type=str), ""
+ )
+ if isinstance(code, str):
+ return re.sub(regex_pattern, "", code)
+
+
+class TransformerNamespace(ComponentNamespace):
+ """Namespace for the Transformers."""
+
+ shikijs = ShikiJsTransformer
+
+
+class CodeblockNamespace(ComponentNamespace):
+ """Namespace for the CodeBlock component."""
+
+ root = staticmethod(ShikiCodeBlock.create)
+ create_transformer = staticmethod(ShikiCodeBlock.create_transformer)
+ transformers = TransformerNamespace()
+ __call__ = staticmethod(ShikiHighLevelCodeBlock.create)
+
+
+code_block = CodeblockNamespace()
diff --git a/reflex/components/datadisplay/shiki_code_block.pyi b/reflex/components/datadisplay/shiki_code_block.pyi
new file mode 100644
index 000000000..bcf2719e9
--- /dev/null
+++ b/reflex/components/datadisplay/shiki_code_block.pyi
@@ -0,0 +1,2211 @@
+"""Stub file for reflex/components/datadisplay/shiki_code_block.py"""
+
+# ------------------- DO NOT EDIT ----------------------
+# This file was generated by `reflex/utils/pyi_generator.py`!
+# ------------------------------------------------------
+from typing import Any, Dict, Literal, Optional, Union, overload
+
+from reflex.base import Base
+from reflex.components.component import Component, ComponentNamespace
+from reflex.event import EventType
+from reflex.style import Style
+from reflex.vars.base import Var
+from reflex.vars.function import FunctionStringVar
+
+def copy_script() -> Any: ...
+
+SHIKIJS_TRANSFORMER_FNS = {
+ "transformerNotationDiff",
+ "transformerNotationHighlight",
+ "transformerNotationWordHighlight",
+ "transformerNotationFocus",
+ "transformerNotationErrorLevel",
+ "transformerRenderWhitespace",
+ "transformerMetaHighlight",
+ "transformerMetaWordHighlight",
+ "transformerCompactLineOptions",
+ "transformerRemoveNotationEscape",
+}
+LINE_NUMBER_STYLING = {
+ "code": {
+ "counter-reset": "step",
+ "counter-increment": "step 0",
+ "display": "grid",
+ "line-height": "1.7",
+ "font-size": "0.875em",
+ },
+ "code .line::before": {
+ "content": "counter(step)",
+ "counter-increment": "step",
+ "width": "1rem",
+ "margin-right": "1.5rem",
+ "display": "inline-block",
+ "text-align": "right",
+ "color": "rgba(115,138,148,.4)",
+ },
+}
+BOX_PARENT_STYLING = {
+ "pre": {
+ "margin": "0",
+ "padding": "24px",
+ "background": "transparent",
+ "overflow-x": "auto",
+ "border-radius": "6px",
+ }
+}
+THEME_MAPPING = {
+ "light": "one-light",
+ "dark": "one-dark-pro",
+ "a11y-dark": "github-dark",
+}
+LANGUAGE_MAPPING = {"bash": "shellscript"}
+LiteralCodeLanguage = Literal[
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "coq",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+]
+LiteralCodeTheme = Literal[
+ "andromeeda",
+ "aurora-x",
+ "ayu-dark",
+ "catppuccin-frappe",
+ "catppuccin-latte",
+ "catppuccin-macchiato",
+ "catppuccin-mocha",
+ "dark-plus",
+ "dracula",
+ "dracula-soft",
+ "everforest-dark",
+ "everforest-light",
+ "github-dark",
+ "github-dark-default",
+ "github-dark-dimmed",
+ "github-dark-high-contrast",
+ "github-light",
+ "github-light-default",
+ "github-light-high-contrast",
+ "houston",
+ "laserwave",
+ "light-plus",
+ "material-theme",
+ "material-theme-darker",
+ "material-theme-lighter",
+ "material-theme-ocean",
+ "material-theme-palenight",
+ "min-dark",
+ "min-light",
+ "monokai",
+ "night-owl",
+ "nord",
+ "one-dark-pro",
+ "one-light",
+ "plain",
+ "plastic",
+ "poimandres",
+ "red",
+ "rose-pine",
+ "rose-pine-dawn",
+ "rose-pine-moon",
+ "slack-dark",
+ "slack-ochin",
+ "snazzy-light",
+ "solarized-dark",
+ "solarized-light",
+ "synthwave-84",
+ "tokyo-night",
+ "vesper",
+ "vitesse-black",
+ "vitesse-dark",
+ "vitesse-light",
+]
+
+class ShikiBaseTransformers(Base):
+ library: str
+ fns: list[FunctionStringVar]
+ style: Optional[Style]
+
+class ShikiJsTransformer(ShikiBaseTransformers):
+ library: str
+ fns: list[FunctionStringVar]
+ style: Optional[Style]
+
+class ShikiCodeBlock(Component):
+ @overload
+ @classmethod
+ def create( # type: ignore
+ cls,
+ *children,
+ language: Optional[
+ Union[
+ Literal[
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "coq",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+ ],
+ Var[
+ Literal[
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "coq",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+ ]
+ ],
+ ]
+ ] = None,
+ theme: Optional[
+ Union[
+ Literal[
+ "andromeeda",
+ "aurora-x",
+ "ayu-dark",
+ "catppuccin-frappe",
+ "catppuccin-latte",
+ "catppuccin-macchiato",
+ "catppuccin-mocha",
+ "dark-plus",
+ "dracula",
+ "dracula-soft",
+ "everforest-dark",
+ "everforest-light",
+ "github-dark",
+ "github-dark-default",
+ "github-dark-dimmed",
+ "github-dark-high-contrast",
+ "github-light",
+ "github-light-default",
+ "github-light-high-contrast",
+ "houston",
+ "laserwave",
+ "light-plus",
+ "material-theme",
+ "material-theme-darker",
+ "material-theme-lighter",
+ "material-theme-ocean",
+ "material-theme-palenight",
+ "min-dark",
+ "min-light",
+ "monokai",
+ "night-owl",
+ "nord",
+ "one-dark-pro",
+ "one-light",
+ "plain",
+ "plastic",
+ "poimandres",
+ "red",
+ "rose-pine",
+ "rose-pine-dawn",
+ "rose-pine-moon",
+ "slack-dark",
+ "slack-ochin",
+ "snazzy-light",
+ "solarized-dark",
+ "solarized-light",
+ "synthwave-84",
+ "tokyo-night",
+ "vesper",
+ "vitesse-black",
+ "vitesse-dark",
+ "vitesse-light",
+ ],
+ Var[
+ Literal[
+ "andromeeda",
+ "aurora-x",
+ "ayu-dark",
+ "catppuccin-frappe",
+ "catppuccin-latte",
+ "catppuccin-macchiato",
+ "catppuccin-mocha",
+ "dark-plus",
+ "dracula",
+ "dracula-soft",
+ "everforest-dark",
+ "everforest-light",
+ "github-dark",
+ "github-dark-default",
+ "github-dark-dimmed",
+ "github-dark-high-contrast",
+ "github-light",
+ "github-light-default",
+ "github-light-high-contrast",
+ "houston",
+ "laserwave",
+ "light-plus",
+ "material-theme",
+ "material-theme-darker",
+ "material-theme-lighter",
+ "material-theme-ocean",
+ "material-theme-palenight",
+ "min-dark",
+ "min-light",
+ "monokai",
+ "night-owl",
+ "nord",
+ "one-dark-pro",
+ "one-light",
+ "plain",
+ "plastic",
+ "poimandres",
+ "red",
+ "rose-pine",
+ "rose-pine-dawn",
+ "rose-pine-moon",
+ "slack-dark",
+ "slack-ochin",
+ "snazzy-light",
+ "solarized-dark",
+ "solarized-light",
+ "synthwave-84",
+ "tokyo-night",
+ "vesper",
+ "vitesse-black",
+ "vitesse-dark",
+ "vitesse-light",
+ ]
+ ],
+ ]
+ ] = None,
+ themes: Optional[
+ Union[
+ Var[Union[dict[str, str], list[dict[str, Any]]]],
+ dict[str, str],
+ list[dict[str, Any]],
+ ]
+ ] = None,
+ code: Optional[Union[Var[str], str]] = None,
+ transformers: Optional[
+ Union[
+ Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]],
+ list[Union[ShikiBaseTransformers, dict[str, Any]]],
+ ]
+ ] = None,
+ style: Optional[Style] = None,
+ key: Optional[Any] = None,
+ id: Optional[Any] = None,
+ class_name: Optional[Any] = None,
+ autofocus: Optional[bool] = None,
+ custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
+ on_blur: Optional[EventType[[]]] = None,
+ on_click: Optional[EventType[[]]] = None,
+ on_context_menu: Optional[EventType[[]]] = None,
+ on_double_click: Optional[EventType[[]]] = None,
+ on_focus: Optional[EventType[[]]] = None,
+ on_mount: Optional[EventType[[]]] = None,
+ on_mouse_down: Optional[EventType[[]]] = None,
+ on_mouse_enter: Optional[EventType[[]]] = None,
+ on_mouse_leave: Optional[EventType[[]]] = None,
+ on_mouse_move: Optional[EventType[[]]] = None,
+ on_mouse_out: Optional[EventType[[]]] = None,
+ on_mouse_over: Optional[EventType[[]]] = None,
+ on_mouse_up: Optional[EventType[[]]] = None,
+ on_scroll: Optional[EventType[[]]] = None,
+ on_unmount: Optional[EventType[[]]] = None,
+ **props,
+ ) -> "ShikiCodeBlock":
+ """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
+
+ Args:
+ *children: The children of the component.
+ language: The language to use.
+ theme: The theme to use ("light" or "dark").
+ themes: The set of themes to use for different modes.
+ code: The code to display.
+ transformers: The transformers to use for the syntax highlighter.
+ style: The style of the component.
+ key: A unique key for the component.
+ id: The id for the component.
+ class_name: The class name for the component.
+ autofocus: Whether the component should take the focus once the page is loaded
+ custom_attrs: custom attribute
+ **props: The props to pass to the component.
+
+ Returns:
+ The code block component.
+ """
+ ...
+
+ def add_imports(self) -> dict[str, list[str]]: ...
+ @classmethod
+ def create_transformer(
+ cls, library: str, fns: list[str]
+ ) -> ShikiBaseTransformers: ...
+
+class ShikiHighLevelCodeBlock(ShikiCodeBlock):
+ @overload
+ @classmethod
+ def create( # type: ignore
+ cls,
+ *children,
+ use_transformers: Optional[Union[Var[bool], bool]] = None,
+ show_line_numbers: Optional[Union[Var[bool], bool]] = None,
+ can_copy: Optional[Union[Var[bool], bool]] = None,
+ copy_button: Optional[
+ Union[Component, Var[Optional[Union[Component, bool]]], bool]
+ ] = None,
+ language: Optional[
+ Union[
+ Literal[
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "coq",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+ ],
+ Var[
+ Literal[
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "coq",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+ ]
+ ],
+ ]
+ ] = None,
+ theme: Optional[
+ Union[
+ Literal[
+ "andromeeda",
+ "aurora-x",
+ "ayu-dark",
+ "catppuccin-frappe",
+ "catppuccin-latte",
+ "catppuccin-macchiato",
+ "catppuccin-mocha",
+ "dark-plus",
+ "dracula",
+ "dracula-soft",
+ "everforest-dark",
+ "everforest-light",
+ "github-dark",
+ "github-dark-default",
+ "github-dark-dimmed",
+ "github-dark-high-contrast",
+ "github-light",
+ "github-light-default",
+ "github-light-high-contrast",
+ "houston",
+ "laserwave",
+ "light-plus",
+ "material-theme",
+ "material-theme-darker",
+ "material-theme-lighter",
+ "material-theme-ocean",
+ "material-theme-palenight",
+ "min-dark",
+ "min-light",
+ "monokai",
+ "night-owl",
+ "nord",
+ "one-dark-pro",
+ "one-light",
+ "plain",
+ "plastic",
+ "poimandres",
+ "red",
+ "rose-pine",
+ "rose-pine-dawn",
+ "rose-pine-moon",
+ "slack-dark",
+ "slack-ochin",
+ "snazzy-light",
+ "solarized-dark",
+ "solarized-light",
+ "synthwave-84",
+ "tokyo-night",
+ "vesper",
+ "vitesse-black",
+ "vitesse-dark",
+ "vitesse-light",
+ ],
+ Var[
+ Literal[
+ "andromeeda",
+ "aurora-x",
+ "ayu-dark",
+ "catppuccin-frappe",
+ "catppuccin-latte",
+ "catppuccin-macchiato",
+ "catppuccin-mocha",
+ "dark-plus",
+ "dracula",
+ "dracula-soft",
+ "everforest-dark",
+ "everforest-light",
+ "github-dark",
+ "github-dark-default",
+ "github-dark-dimmed",
+ "github-dark-high-contrast",
+ "github-light",
+ "github-light-default",
+ "github-light-high-contrast",
+ "houston",
+ "laserwave",
+ "light-plus",
+ "material-theme",
+ "material-theme-darker",
+ "material-theme-lighter",
+ "material-theme-ocean",
+ "material-theme-palenight",
+ "min-dark",
+ "min-light",
+ "monokai",
+ "night-owl",
+ "nord",
+ "one-dark-pro",
+ "one-light",
+ "plain",
+ "plastic",
+ "poimandres",
+ "red",
+ "rose-pine",
+ "rose-pine-dawn",
+ "rose-pine-moon",
+ "slack-dark",
+ "slack-ochin",
+ "snazzy-light",
+ "solarized-dark",
+ "solarized-light",
+ "synthwave-84",
+ "tokyo-night",
+ "vesper",
+ "vitesse-black",
+ "vitesse-dark",
+ "vitesse-light",
+ ]
+ ],
+ ]
+ ] = None,
+ themes: Optional[
+ Union[
+ Var[Union[dict[str, str], list[dict[str, Any]]]],
+ dict[str, str],
+ list[dict[str, Any]],
+ ]
+ ] = None,
+ code: Optional[Union[Var[str], str]] = None,
+ transformers: Optional[
+ Union[
+ Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]],
+ list[Union[ShikiBaseTransformers, dict[str, Any]]],
+ ]
+ ] = None,
+ style: Optional[Style] = None,
+ key: Optional[Any] = None,
+ id: Optional[Any] = None,
+ class_name: Optional[Any] = None,
+ autofocus: Optional[bool] = None,
+ custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
+ on_blur: Optional[EventType[[]]] = None,
+ on_click: Optional[EventType[[]]] = None,
+ on_context_menu: Optional[EventType[[]]] = None,
+ on_double_click: Optional[EventType[[]]] = None,
+ on_focus: Optional[EventType[[]]] = None,
+ on_mount: Optional[EventType[[]]] = None,
+ on_mouse_down: Optional[EventType[[]]] = None,
+ on_mouse_enter: Optional[EventType[[]]] = None,
+ on_mouse_leave: Optional[EventType[[]]] = None,
+ on_mouse_move: Optional[EventType[[]]] = None,
+ on_mouse_out: Optional[EventType[[]]] = None,
+ on_mouse_over: Optional[EventType[[]]] = None,
+ on_mouse_up: Optional[EventType[[]]] = None,
+ on_scroll: Optional[EventType[[]]] = None,
+ on_unmount: Optional[EventType[[]]] = None,
+ **props,
+ ) -> "ShikiHighLevelCodeBlock":
+ """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
+
+ Args:
+ *children: The children of the component.
+ use_transformers: If this is enabled, the default transformers(shikijs transformer) will be used.
+ show_line_numbers: If this is enabled line numbers will be shown next to the code block.
+ can_copy: Whether a copy button should appear.
+ copy_button: copy_button: A custom copy button to override the default one.
+ language: The language to use.
+ theme: The theme to use ("light" or "dark").
+ themes: The set of themes to use for different modes.
+ code: The code to display.
+ transformers: The transformers to use for the syntax highlighter.
+ style: The style of the component.
+ key: A unique key for the component.
+ id: The id for the component.
+ class_name: The class name for the component.
+ autofocus: Whether the component should take the focus once the page is loaded
+ custom_attrs: custom attribute
+ **props: The props to pass to the component.
+
+ Returns:
+ The code block component.
+ """
+ ...
+
+class TransformerNamespace(ComponentNamespace):
+ shikijs = ShikiJsTransformer
+
+class CodeblockNamespace(ComponentNamespace):
+ root = staticmethod(ShikiCodeBlock.create)
+ create_transformer = staticmethod(ShikiCodeBlock.create_transformer)
+ transformers = TransformerNamespace()
+
+ @staticmethod
+ def __call__(
+ *children,
+ use_transformers: Optional[Union[Var[bool], bool]] = None,
+ show_line_numbers: Optional[Union[Var[bool], bool]] = None,
+ can_copy: Optional[Union[Var[bool], bool]] = None,
+ copy_button: Optional[
+ Union[Component, Var[Optional[Union[Component, bool]]], bool]
+ ] = None,
+ language: Optional[
+ Union[
+ Literal[
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "coq",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+ ],
+ Var[
+ Literal[
+ "abap",
+ "actionscript-3",
+ "ada",
+ "angular-html",
+ "angular-ts",
+ "apache",
+ "apex",
+ "apl",
+ "applescript",
+ "ara",
+ "asciidoc",
+ "asm",
+ "astro",
+ "awk",
+ "ballerina",
+ "bat",
+ "beancount",
+ "berry",
+ "bibtex",
+ "bicep",
+ "blade",
+ "c",
+ "cadence",
+ "clarity",
+ "clojure",
+ "cmake",
+ "cobol",
+ "codeowners",
+ "codeql",
+ "coffee",
+ "common-lisp",
+ "coq",
+ "cpp",
+ "crystal",
+ "csharp",
+ "css",
+ "csv",
+ "cue",
+ "cypher",
+ "d",
+ "dart",
+ "dax",
+ "desktop",
+ "diff",
+ "docker",
+ "dotenv",
+ "dream-maker",
+ "edge",
+ "elixir",
+ "elm",
+ "emacs-lisp",
+ "erb",
+ "erlang",
+ "fennel",
+ "fish",
+ "fluent",
+ "fortran-fixed-form",
+ "fortran-free-form",
+ "fsharp",
+ "gdresource",
+ "gdscript",
+ "gdshader",
+ "genie",
+ "gherkin",
+ "git-commit",
+ "git-rebase",
+ "gleam",
+ "glimmer-js",
+ "glimmer-ts",
+ "glsl",
+ "gnuplot",
+ "go",
+ "graphql",
+ "groovy",
+ "hack",
+ "haml",
+ "handlebars",
+ "haskell",
+ "haxe",
+ "hcl",
+ "hjson",
+ "hlsl",
+ "html",
+ "html-derivative",
+ "http",
+ "hxml",
+ "hy",
+ "imba",
+ "ini",
+ "java",
+ "javascript",
+ "jinja",
+ "jison",
+ "json",
+ "json5",
+ "jsonc",
+ "jsonl",
+ "jsonnet",
+ "jssm",
+ "jsx",
+ "julia",
+ "kotlin",
+ "kusto",
+ "latex",
+ "lean",
+ "less",
+ "liquid",
+ "log",
+ "logo",
+ "lua",
+ "luau",
+ "make",
+ "markdown",
+ "marko",
+ "matlab",
+ "mdc",
+ "mdx",
+ "mermaid",
+ "mojo",
+ "move",
+ "narrat",
+ "nextflow",
+ "nginx",
+ "nim",
+ "nix",
+ "nushell",
+ "objective-c",
+ "objective-cpp",
+ "ocaml",
+ "pascal",
+ "perl",
+ "php",
+ "plsql",
+ "po",
+ "postcss",
+ "powerquery",
+ "powershell",
+ "prisma",
+ "prolog",
+ "proto",
+ "pug",
+ "puppet",
+ "purescript",
+ "python",
+ "qml",
+ "qmldir",
+ "qss",
+ "r",
+ "racket",
+ "raku",
+ "razor",
+ "reg",
+ "regexp",
+ "rel",
+ "riscv",
+ "rst",
+ "ruby",
+ "rust",
+ "sas",
+ "sass",
+ "scala",
+ "scheme",
+ "scss",
+ "shaderlab",
+ "shellscript",
+ "shellsession",
+ "smalltalk",
+ "solidity",
+ "soy",
+ "sparql",
+ "splunk",
+ "sql",
+ "ssh-config",
+ "stata",
+ "stylus",
+ "svelte",
+ "swift",
+ "system-verilog",
+ "systemd",
+ "tasl",
+ "tcl",
+ "templ",
+ "terraform",
+ "tex",
+ "toml",
+ "ts-tags",
+ "tsv",
+ "tsx",
+ "turtle",
+ "twig",
+ "typescript",
+ "typespec",
+ "typst",
+ "v",
+ "vala",
+ "vb",
+ "verilog",
+ "vhdl",
+ "viml",
+ "vue",
+ "vue-html",
+ "vyper",
+ "wasm",
+ "wenyan",
+ "wgsl",
+ "wikitext",
+ "wolfram",
+ "xml",
+ "xsl",
+ "yaml",
+ "zenscript",
+ "zig",
+ ]
+ ],
+ ]
+ ] = None,
+ theme: Optional[
+ Union[
+ Literal[
+ "andromeeda",
+ "aurora-x",
+ "ayu-dark",
+ "catppuccin-frappe",
+ "catppuccin-latte",
+ "catppuccin-macchiato",
+ "catppuccin-mocha",
+ "dark-plus",
+ "dracula",
+ "dracula-soft",
+ "everforest-dark",
+ "everforest-light",
+ "github-dark",
+ "github-dark-default",
+ "github-dark-dimmed",
+ "github-dark-high-contrast",
+ "github-light",
+ "github-light-default",
+ "github-light-high-contrast",
+ "houston",
+ "laserwave",
+ "light-plus",
+ "material-theme",
+ "material-theme-darker",
+ "material-theme-lighter",
+ "material-theme-ocean",
+ "material-theme-palenight",
+ "min-dark",
+ "min-light",
+ "monokai",
+ "night-owl",
+ "nord",
+ "one-dark-pro",
+ "one-light",
+ "plain",
+ "plastic",
+ "poimandres",
+ "red",
+ "rose-pine",
+ "rose-pine-dawn",
+ "rose-pine-moon",
+ "slack-dark",
+ "slack-ochin",
+ "snazzy-light",
+ "solarized-dark",
+ "solarized-light",
+ "synthwave-84",
+ "tokyo-night",
+ "vesper",
+ "vitesse-black",
+ "vitesse-dark",
+ "vitesse-light",
+ ],
+ Var[
+ Literal[
+ "andromeeda",
+ "aurora-x",
+ "ayu-dark",
+ "catppuccin-frappe",
+ "catppuccin-latte",
+ "catppuccin-macchiato",
+ "catppuccin-mocha",
+ "dark-plus",
+ "dracula",
+ "dracula-soft",
+ "everforest-dark",
+ "everforest-light",
+ "github-dark",
+ "github-dark-default",
+ "github-dark-dimmed",
+ "github-dark-high-contrast",
+ "github-light",
+ "github-light-default",
+ "github-light-high-contrast",
+ "houston",
+ "laserwave",
+ "light-plus",
+ "material-theme",
+ "material-theme-darker",
+ "material-theme-lighter",
+ "material-theme-ocean",
+ "material-theme-palenight",
+ "min-dark",
+ "min-light",
+ "monokai",
+ "night-owl",
+ "nord",
+ "one-dark-pro",
+ "one-light",
+ "plain",
+ "plastic",
+ "poimandres",
+ "red",
+ "rose-pine",
+ "rose-pine-dawn",
+ "rose-pine-moon",
+ "slack-dark",
+ "slack-ochin",
+ "snazzy-light",
+ "solarized-dark",
+ "solarized-light",
+ "synthwave-84",
+ "tokyo-night",
+ "vesper",
+ "vitesse-black",
+ "vitesse-dark",
+ "vitesse-light",
+ ]
+ ],
+ ]
+ ] = None,
+ themes: Optional[
+ Union[
+ Var[Union[dict[str, str], list[dict[str, Any]]]],
+ dict[str, str],
+ list[dict[str, Any]],
+ ]
+ ] = None,
+ code: Optional[Union[Var[str], str]] = None,
+ transformers: Optional[
+ Union[
+ Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]],
+ list[Union[ShikiBaseTransformers, dict[str, Any]]],
+ ]
+ ] = None,
+ style: Optional[Style] = None,
+ key: Optional[Any] = None,
+ id: Optional[Any] = None,
+ class_name: Optional[Any] = None,
+ autofocus: Optional[bool] = None,
+ custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
+ on_blur: Optional[EventType[[]]] = None,
+ on_click: Optional[EventType[[]]] = None,
+ on_context_menu: Optional[EventType[[]]] = None,
+ on_double_click: Optional[EventType[[]]] = None,
+ on_focus: Optional[EventType[[]]] = None,
+ on_mount: Optional[EventType[[]]] = None,
+ on_mouse_down: Optional[EventType[[]]] = None,
+ on_mouse_enter: Optional[EventType[[]]] = None,
+ on_mouse_leave: Optional[EventType[[]]] = None,
+ on_mouse_move: Optional[EventType[[]]] = None,
+ on_mouse_out: Optional[EventType[[]]] = None,
+ on_mouse_over: Optional[EventType[[]]] = None,
+ on_mouse_up: Optional[EventType[[]]] = None,
+ on_scroll: Optional[EventType[[]]] = None,
+ on_unmount: Optional[EventType[[]]] = None,
+ **props,
+ ) -> "ShikiHighLevelCodeBlock":
+ """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
+
+ Args:
+ *children: The children of the component.
+ use_transformers: If this is enabled, the default transformers(shikijs transformer) will be used.
+ show_line_numbers: If this is enabled line numbers will be shown next to the code block.
+ can_copy: Whether a copy button should appear.
+ copy_button: copy_button: A custom copy button to override the default one.
+ language: The language to use.
+ theme: The theme to use ("light" or "dark").
+ themes: The set of themes to use for different modes.
+ code: The code to display.
+ transformers: The transformers to use for the syntax highlighter.
+ style: The style of the component.
+ key: A unique key for the component.
+ id: The id for the component.
+ class_name: The class name for the component.
+ autofocus: Whether the component should take the focus once the page is loaded
+ custom_attrs: custom attribute
+ **props: The props to pass to the component.
+
+ Returns:
+ The code block component.
+ """
+ ...
+
+code_block = CodeblockNamespace()
diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py
index 1665144fd..b790bf7a1 100644
--- a/reflex/components/markdown/markdown.py
+++ b/reflex/components/markdown/markdown.py
@@ -20,6 +20,8 @@ from reflex.components.tags.tag import Tag
from reflex.utils import types
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars.base import LiteralVar, Var
+from reflex.vars.function import ARRAY_ISARRAY
+from reflex.vars.number import ternary_operation
# Special vars used in the component map.
_CHILDREN = Var(_js_expr="children", _var_type=str)
@@ -199,7 +201,16 @@ class Markdown(Component):
raise ValueError(f"No markdown component found for tag: {tag}.")
special_props = [_PROPS_IN_TAG]
- children = [_CHILDREN]
+ children = [
+ _CHILDREN
+ if tag != "codeblock"
+ # For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string.
+ else ternary_operation(
+ ARRAY_ISARRAY.call(_CHILDREN), # type: ignore
+ _CHILDREN.to(list).join("\n"),
+ _CHILDREN,
+ ).to(str)
+ ]
# For certain tags, the props from the markdown renderer are not actually valid for the component.
if tag in NO_PROPS_TAGS:
diff --git a/reflex/components/moment/moment.pyi b/reflex/components/moment/moment.pyi
index 4f58fda7d..ccffbb8d1 100644
--- a/reflex/components/moment/moment.pyi
+++ b/reflex/components/moment/moment.pyi
@@ -58,7 +58,7 @@ class Moment(NoSSRComponent):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[str]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
diff --git a/reflex/components/radix/primitives/drawer.pyi b/reflex/components/radix/primitives/drawer.pyi
index 9c5463e56..c4493ee9b 100644
--- a/reflex/components/radix/primitives/drawer.pyi
+++ b/reflex/components/radix/primitives/drawer.pyi
@@ -101,7 +101,7 @@ class DrawerRoot(DrawerComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
@@ -511,7 +511,7 @@ class Drawer(ComponentNamespace):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/radix/themes/color_mode.pyi b/reflex/components/radix/themes/color_mode.pyi
index 43703dc37..d856b0bbd 100644
--- a/reflex/components/radix/themes/color_mode.pyi
+++ b/reflex/components/radix/themes/color_mode.pyi
@@ -383,7 +383,7 @@ class ColorModeSwitch(Switch):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[bool]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
diff --git a/reflex/components/radix/themes/components/alert_dialog.pyi b/reflex/components/radix/themes/components/alert_dialog.pyi
index d63fcae53..771def7d9 100644
--- a/reflex/components/radix/themes/components/alert_dialog.pyi
+++ b/reflex/components/radix/themes/components/alert_dialog.pyi
@@ -42,7 +42,7 @@ class AlertDialogRoot(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/radix/themes/components/checkbox.pyi b/reflex/components/radix/themes/components/checkbox.pyi
index fad4f5210..90a9220ef 100644
--- a/reflex/components/radix/themes/components/checkbox.pyi
+++ b/reflex/components/radix/themes/components/checkbox.pyi
@@ -116,7 +116,7 @@ class Checkbox(RadixThemesComponent):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[bool]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
@@ -263,7 +263,7 @@ class HighLevelCheckbox(RadixThemesComponent):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[bool]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
@@ -407,7 +407,7 @@ class CheckboxNamespace(ComponentNamespace):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[bool]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
diff --git a/reflex/components/radix/themes/components/context_menu.pyi b/reflex/components/radix/themes/components/context_menu.pyi
index fbefa88de..56d8200e0 100644
--- a/reflex/components/radix/themes/components/context_menu.pyi
+++ b/reflex/components/radix/themes/components/context_menu.pyi
@@ -39,7 +39,7 @@ class ContextMenuRoot(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/radix/themes/components/dialog.pyi b/reflex/components/radix/themes/components/dialog.pyi
index e3f17d7e8..0713461e9 100644
--- a/reflex/components/radix/themes/components/dialog.pyi
+++ b/reflex/components/radix/themes/components/dialog.pyi
@@ -40,7 +40,7 @@ class DialogRoot(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
@@ -382,7 +382,7 @@ class Dialog(ComponentNamespace):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/radix/themes/components/dropdown_menu.pyi b/reflex/components/radix/themes/components/dropdown_menu.pyi
index dba619e7d..8de273be9 100644
--- a/reflex/components/radix/themes/components/dropdown_menu.pyi
+++ b/reflex/components/radix/themes/components/dropdown_menu.pyi
@@ -49,7 +49,7 @@ class DropdownMenuRoot(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
@@ -363,7 +363,7 @@ class DropdownMenuSub(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/radix/themes/components/hover_card.pyi b/reflex/components/radix/themes/components/hover_card.pyi
index 8924ef1a8..fa169c852 100644
--- a/reflex/components/radix/themes/components/hover_card.pyi
+++ b/reflex/components/radix/themes/components/hover_card.pyi
@@ -43,7 +43,7 @@ class HoverCardRoot(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
@@ -256,7 +256,7 @@ class HoverCard(ComponentNamespace):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/radix/themes/components/popover.pyi b/reflex/components/radix/themes/components/popover.pyi
index 984a139d0..218392517 100644
--- a/reflex/components/radix/themes/components/popover.pyi
+++ b/reflex/components/radix/themes/components/popover.pyi
@@ -41,7 +41,7 @@ class PopoverRoot(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/radix/themes/components/radio_cards.pyi b/reflex/components/radix/themes/components/radio_cards.pyi
index d73447622..d2f6a1425 100644
--- a/reflex/components/radix/themes/components/radio_cards.pyi
+++ b/reflex/components/radix/themes/components/radio_cards.pyi
@@ -177,7 +177,7 @@ class RadioCardsRoot(RadixThemesComponent):
on_mouse_up: Optional[EventType[[]]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
- on_value_change: Optional[EventType] = None,
+ on_value_change: Optional[EventType[str]] = None,
**props,
) -> "RadioCardsRoot":
"""Create a new component instance.
diff --git a/reflex/components/radix/themes/components/radio_group.pyi b/reflex/components/radix/themes/components/radio_group.pyi
index c984fa1f2..f928421c5 100644
--- a/reflex/components/radix/themes/components/radio_group.pyi
+++ b/reflex/components/radix/themes/components/radio_group.pyi
@@ -113,7 +113,7 @@ class RadioGroupRoot(RadixThemesComponent):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[str]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
diff --git a/reflex/components/radix/themes/components/select.pyi b/reflex/components/radix/themes/components/select.pyi
index c43d58ada..e0e184482 100644
--- a/reflex/components/radix/themes/components/select.pyi
+++ b/reflex/components/radix/themes/components/select.pyi
@@ -44,7 +44,7 @@ class SelectRoot(RadixThemesComponent):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[str]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
@@ -57,7 +57,7 @@ class SelectRoot(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
@@ -680,7 +680,7 @@ class HighLevelSelect(SelectRoot):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[str]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
@@ -693,7 +693,7 @@ class HighLevelSelect(SelectRoot):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
@@ -854,7 +854,7 @@ class Select(ComponentNamespace):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[str]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
@@ -867,7 +867,7 @@ class Select(ComponentNamespace):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/radix/themes/components/switch.pyi b/reflex/components/radix/themes/components/switch.pyi
index ba9c2595e..f8871872a 100644
--- a/reflex/components/radix/themes/components/switch.pyi
+++ b/reflex/components/radix/themes/components/switch.pyi
@@ -119,7 +119,7 @@ class Switch(RadixThemesComponent):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[bool]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
diff --git a/reflex/components/radix/themes/components/tabs.pyi b/reflex/components/radix/themes/components/tabs.pyi
index 7b67bad6e..4272bf2a3 100644
--- a/reflex/components/radix/themes/components/tabs.pyi
+++ b/reflex/components/radix/themes/components/tabs.pyi
@@ -41,7 +41,7 @@ class TabsRoot(RadixThemesComponent):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[str]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
@@ -340,7 +340,7 @@ class Tabs(ComponentNamespace):
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
on_blur: Optional[EventType[[]]] = None,
- on_change: Optional[EventType] = None,
+ on_change: Optional[EventType[str]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
diff --git a/reflex/components/radix/themes/components/tooltip.pyi b/reflex/components/radix/themes/components/tooltip.pyi
index ad7c4402f..e78dd926d 100644
--- a/reflex/components/radix/themes/components/tooltip.pyi
+++ b/reflex/components/radix/themes/components/tooltip.pyi
@@ -5,7 +5,9 @@
# ------------------------------------------------------
from typing import Any, Dict, Literal, Optional, Union, overload
-from reflex.event import EventType
+from reflex.event import (
+ EventType,
+)
from reflex.style import Style
from reflex.vars.base import Var
@@ -76,7 +78,7 @@ class Tooltip(RadixThemesComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_pointer_down_outside: Optional[EventType[[]]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
diff --git a/reflex/components/react_player/__init__.py b/reflex/components/react_player/__init__.py
index 8c4a4486f..3f807b1a0 100644
--- a/reflex/components/react_player/__init__.py
+++ b/reflex/components/react_player/__init__.py
@@ -1,5 +1,6 @@
"""React Player component for audio and video."""
+from . import react_player
from .audio import Audio
from .video import Video
diff --git a/reflex/components/react_player/audio.pyi b/reflex/components/react_player/audio.pyi
index d1f29f508..1841829af 100644
--- a/reflex/components/react_player/audio.pyi
+++ b/reflex/components/react_player/audio.pyi
@@ -5,6 +5,7 @@
# ------------------------------------------------------
from typing import Any, Dict, Optional, Union, overload
+import reflex
from reflex.components.react_player.react_player import ReactPlayer
from reflex.event import EventType
from reflex.style import Style
@@ -41,7 +42,7 @@ class Audio(ReactPlayer):
on_context_menu: Optional[EventType[[]]] = None,
on_disable_pip: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
- on_duration: Optional[EventType] = None,
+ on_duration: Optional[EventType[float]] = None,
on_enable_pip: Optional[EventType[[]]] = None,
on_ended: Optional[EventType[[]]] = None,
on_error: Optional[EventType[[]]] = None,
@@ -58,10 +59,12 @@ class Audio(ReactPlayer):
on_play: Optional[EventType[[]]] = None,
on_playback_quality_change: Optional[EventType[[]]] = None,
on_playback_rate_change: Optional[EventType[[]]] = None,
- on_progress: Optional[EventType] = None,
+ on_progress: Optional[
+ EventType[reflex.components.react_player.react_player.Progress]
+ ] = None,
on_ready: Optional[EventType[[]]] = None,
on_scroll: Optional[EventType[[]]] = None,
- on_seek: Optional[EventType] = None,
+ on_seek: Optional[EventType[float]] = None,
on_start: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/react_player/react_player.py b/reflex/components/react_player/react_player.py
index 7ad45b093..b2c58b754 100644
--- a/reflex/components/react_player/react_player.py
+++ b/reflex/components/react_player/react_player.py
@@ -2,11 +2,22 @@
from __future__ import annotations
+from typing_extensions import TypedDict
+
from reflex.components.component import NoSSRComponent
from reflex.event import EventHandler, empty_event, identity_event
from reflex.vars.base import Var
+class Progress(TypedDict):
+ """Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds."""
+
+ played: float
+ playedSeconds: float
+ loaded: float
+ loadedSeconds: float
+
+
class ReactPlayer(NoSSRComponent):
"""Using react-player and not implement all props and callback yet.
reference: https://github.com/cookpete/react-player.
@@ -55,7 +66,7 @@ class ReactPlayer(NoSSRComponent):
on_play: EventHandler[empty_event]
# Callback containing played and loaded progress as a fraction, and playedSeconds and loadedSeconds in seconds. eg { played: 0.12, playedSeconds: 11.3, loaded: 0.34, loadedSeconds: 16.7 }
- on_progress: EventHandler[lambda progress: [progress]]
+ on_progress: EventHandler[identity_event(Progress)]
# Callback containing duration of the media, in seconds.
on_duration: EventHandler[identity_event(float)]
diff --git a/reflex/components/react_player/react_player.pyi b/reflex/components/react_player/react_player.pyi
index 940b09e51..e4027cf40 100644
--- a/reflex/components/react_player/react_player.pyi
+++ b/reflex/components/react_player/react_player.pyi
@@ -5,11 +5,19 @@
# ------------------------------------------------------
from typing import Any, Dict, Optional, Union, overload
+from typing_extensions import TypedDict
+
from reflex.components.component import NoSSRComponent
from reflex.event import EventType
from reflex.style import Style
from reflex.vars.base import Var
+class Progress(TypedDict):
+ played: float
+ playedSeconds: float
+ loaded: float
+ loadedSeconds: float
+
class ReactPlayer(NoSSRComponent):
@overload
@classmethod
@@ -39,7 +47,7 @@ class ReactPlayer(NoSSRComponent):
on_context_menu: Optional[EventType[[]]] = None,
on_disable_pip: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
- on_duration: Optional[EventType] = None,
+ on_duration: Optional[EventType[float]] = None,
on_enable_pip: Optional[EventType[[]]] = None,
on_ended: Optional[EventType[[]]] = None,
on_error: Optional[EventType[[]]] = None,
@@ -56,10 +64,10 @@ class ReactPlayer(NoSSRComponent):
on_play: Optional[EventType[[]]] = None,
on_playback_quality_change: Optional[EventType[[]]] = None,
on_playback_rate_change: Optional[EventType[[]]] = None,
- on_progress: Optional[EventType] = None,
+ on_progress: Optional[EventType[Progress]] = None,
on_ready: Optional[EventType[[]]] = None,
on_scroll: Optional[EventType[[]]] = None,
- on_seek: Optional[EventType] = None,
+ on_seek: Optional[EventType[float]] = None,
on_start: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/react_player/video.pyi b/reflex/components/react_player/video.pyi
index a50ccf71f..a05e3747b 100644
--- a/reflex/components/react_player/video.pyi
+++ b/reflex/components/react_player/video.pyi
@@ -5,6 +5,7 @@
# ------------------------------------------------------
from typing import Any, Dict, Optional, Union, overload
+import reflex
from reflex.components.react_player.react_player import ReactPlayer
from reflex.event import EventType
from reflex.style import Style
@@ -41,7 +42,7 @@ class Video(ReactPlayer):
on_context_menu: Optional[EventType[[]]] = None,
on_disable_pip: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
- on_duration: Optional[EventType] = None,
+ on_duration: Optional[EventType[float]] = None,
on_enable_pip: Optional[EventType[[]]] = None,
on_ended: Optional[EventType[[]]] = None,
on_error: Optional[EventType[[]]] = None,
@@ -58,10 +59,12 @@ class Video(ReactPlayer):
on_play: Optional[EventType[[]]] = None,
on_playback_quality_change: Optional[EventType[[]]] = None,
on_playback_rate_change: Optional[EventType[[]]] = None,
- on_progress: Optional[EventType] = None,
+ on_progress: Optional[
+ EventType[reflex.components.react_player.react_player.Progress]
+ ] = None,
on_ready: Optional[EventType[[]]] = None,
on_scroll: Optional[EventType[[]]] = None,
- on_seek: Optional[EventType] = None,
+ on_seek: Optional[EventType[float]] = None,
on_start: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/components/suneditor/editor.py b/reflex/components/suneditor/editor.py
index 6f5b6e4b4..3bca8a3f6 100644
--- a/reflex/components/suneditor/editor.py
+++ b/reflex/components/suneditor/editor.py
@@ -3,11 +3,11 @@
from __future__ import annotations
import enum
-from typing import Dict, List, Literal, Optional, Union
+from typing import Dict, List, Literal, Optional, Tuple, Union
from reflex.base import Base
from reflex.components.component import Component, NoSSRComponent
-from reflex.event import EventHandler
+from reflex.event import EventHandler, empty_event, identity_event
from reflex.utils.format import to_camel_case
from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars.base import Var
@@ -68,6 +68,35 @@ class EditorOptions(Base):
button_list: Optional[List[Union[List[str], str]]]
+def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]:
+ """A helper function to specify the on_blur event handler.
+
+ Args:
+ e: The event.
+ content: The content of the editor.
+
+ Returns:
+ A tuple containing the content of the editor.
+ """
+ return (content,)
+
+
+def on_paste_spec(
+ e: Var, clean_data: Var[str], max_char_count: Var[bool]
+) -> Tuple[Var[str], Var[bool]]:
+ """A helper function to specify the on_paste event handler.
+
+ Args:
+ e: The event.
+ clean_data: The clean data.
+ max_char_count: The maximum character count.
+
+ Returns:
+ A tuple containing the clean data and the maximum character count.
+ """
+ return (clean_data, max_char_count)
+
+
class Editor(NoSSRComponent):
"""A Rich Text Editor component based on SunEditor.
Not every JS prop is listed here (some are not easily usable from python),
@@ -178,36 +207,31 @@ class Editor(NoSSRComponent):
disable_toolbar: Var[bool]
# Fired when the editor content changes.
- on_change: EventHandler[lambda content: [content]]
+ on_change: EventHandler[identity_event(str)]
# Fired when the something is inputted in the editor.
- on_input: EventHandler[lambda e: [e]]
+ on_input: EventHandler[empty_event]
# Fired when the editor loses focus.
- on_blur: EventHandler[lambda e, content: [content]]
+ on_blur: EventHandler[on_blur_spec]
# Fired when the editor is loaded.
- on_load: EventHandler[lambda reload: [reload]]
-
- # Fired when the editor is resized.
- on_resize_editor: EventHandler[lambda height, prev_height: [height, prev_height]]
+ on_load: EventHandler[identity_event(bool)]
# Fired when the editor content is copied.
- on_copy: EventHandler[lambda e, clipboard_data: [clipboard_data]]
+ on_copy: EventHandler[empty_event]
# Fired when the editor content is cut.
- on_cut: EventHandler[lambda e, clipboard_data: [clipboard_data]]
+ on_cut: EventHandler[empty_event]
# Fired when the editor content is pasted.
- on_paste: EventHandler[
- lambda e, clean_data, max_char_count: [clean_data, max_char_count]
- ]
+ on_paste: EventHandler[on_paste_spec]
# Fired when the code view is toggled.
- toggle_code_view: EventHandler[lambda is_code_view: [is_code_view]]
+ toggle_code_view: EventHandler[identity_event(bool)]
# Fired when the full screen mode is toggled.
- toggle_full_screen: EventHandler[lambda is_full_screen: [is_full_screen]]
+ toggle_full_screen: EventHandler[identity_event(bool)]
def add_imports(self) -> ImportDict:
"""Add imports for the Editor component.
diff --git a/reflex/components/suneditor/editor.pyi b/reflex/components/suneditor/editor.pyi
index f7149a02c..73dd38fdc 100644
--- a/reflex/components/suneditor/editor.pyi
+++ b/reflex/components/suneditor/editor.pyi
@@ -4,7 +4,7 @@
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
import enum
-from typing import Any, Dict, List, Literal, Optional, Union, overload
+from typing import Any, Dict, List, Literal, Optional, Tuple, Union, overload
from reflex.base import Base
from reflex.components.component import NoSSRComponent
@@ -44,6 +44,11 @@ class EditorOptions(Base):
rtl: Optional[bool]
button_list: Optional[List[Union[List[str], str]]]
+def on_blur_spec(e: Var, content: Var[str]) -> Tuple[Var[str]]: ...
+def on_paste_spec(
+ e: Var, clean_data: Var[str], max_char_count: Var[bool]
+) -> Tuple[Var[str], Var[bool]]: ...
+
class Editor(NoSSRComponent):
def add_imports(self) -> ImportDict: ...
@overload
@@ -122,16 +127,16 @@ class Editor(NoSSRComponent):
class_name: Optional[Any] = None,
autofocus: Optional[bool] = None,
custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
- on_blur: Optional[EventType] = None,
- on_change: Optional[EventType] = None,
+ on_blur: Optional[EventType[str]] = None,
+ on_change: Optional[EventType[str]] = None,
on_click: Optional[EventType[[]]] = None,
on_context_menu: Optional[EventType[[]]] = None,
- on_copy: Optional[EventType] = None,
- on_cut: Optional[EventType] = None,
+ on_copy: Optional[EventType[[]]] = None,
+ on_cut: Optional[EventType[[]]] = None,
on_double_click: Optional[EventType[[]]] = None,
on_focus: Optional[EventType[[]]] = None,
- on_input: Optional[EventType] = None,
- on_load: Optional[EventType] = None,
+ on_input: Optional[EventType[[]]] = None,
+ on_load: Optional[EventType[bool]] = None,
on_mount: Optional[EventType[[]]] = None,
on_mouse_down: Optional[EventType[[]]] = None,
on_mouse_enter: Optional[EventType[[]]] = None,
@@ -140,12 +145,11 @@ class Editor(NoSSRComponent):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_paste: Optional[EventType] = None,
- on_resize_editor: Optional[EventType] = None,
+ on_paste: Optional[EventType[str, bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
- toggle_code_view: Optional[EventType] = None,
- toggle_full_screen: Optional[EventType] = None,
+ toggle_code_view: Optional[EventType[bool]] = None,
+ toggle_full_screen: Optional[EventType[bool]] = None,
**props,
) -> "Editor":
"""Create an instance of Editor. No children allowed.
diff --git a/reflex/config.py b/reflex/config.py
index 50eeb9bdd..036812416 100644
--- a/reflex/config.py
+++ b/reflex/config.py
@@ -432,6 +432,12 @@ class Config(Base):
# Number of gunicorn workers from user
gunicorn_workers: Optional[int] = None
+ # Number of requests before a worker is restarted
+ gunicorn_max_requests: int = 100
+
+ # Variance limit for max requests; gunicorn only
+ gunicorn_max_requests_jitter: int = 25
+
# Indicate which type of state manager to use
state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK
@@ -444,6 +450,9 @@ class Config(Base):
# Attributes that were explicitly set by the user.
_non_default_attributes: Set[str] = pydantic.PrivateAttr(set())
+ # Path to file containing key-values pairs to override in the environment; Dotenv format.
+ env_file: Optional[str] = None
+
def __init__(self, *args, **kwargs):
"""Initialize the config values.
@@ -485,10 +494,19 @@ class Config(Base):
def update_from_env(self) -> dict[str, Any]:
"""Update the config values based on set environment variables.
+ If there is a set env_file, it is loaded first.
Returns:
The updated config values.
"""
+ from reflex.utils.exceptions import EnvVarValueError
+
+ if self.env_file:
+ from dotenv import load_dotenv
+
+ # load env file if exists
+ load_dotenv(self.env_file, override=True)
+
updated_values = {}
# Iterate over the fields.
for key, field in self.__fields__.items():
diff --git a/reflex/constants/compiler.py b/reflex/constants/compiler.py
index 557a92092..318a93783 100644
--- a/reflex/constants/compiler.py
+++ b/reflex/constants/compiler.py
@@ -132,16 +132,6 @@ class Hooks(SimpleNamespace):
}
})"""
- FRONTEND_ERRORS = f"""
- const logFrontendError = (error, info) => {{
- if (process.env.NODE_ENV === "production") {{
- addEvents([Event("{CompileVars.FRONTEND_EXCEPTION_STATE_FULL}.handle_frontend_exception", {{
- stack: error.stack,
- }})])
- }}
- }}
- """
-
class MemoizationDisposition(enum.Enum):
"""The conditions under which a component should be memoized."""
diff --git a/reflex/event.py b/reflex/event.py
index 8291e3465..cfe40ef9a 100644
--- a/reflex/event.py
+++ b/reflex/event.py
@@ -12,7 +12,6 @@ from functools import partial
from typing import (
Any,
Callable,
- ClassVar,
Dict,
Generic,
List,
@@ -25,7 +24,7 @@ from typing import (
overload,
)
-from typing_extensions import ParamSpec, get_args, get_origin
+from typing_extensions import ParamSpec, Protocol, get_args, get_origin
from reflex import constants
from reflex.utils import console, format
@@ -33,9 +32,7 @@ from reflex.utils.exceptions import EventFnArgMismatch, EventHandlerArgMismatch
from reflex.utils.types import ArgsSpec, GenericType
from reflex.vars import VarData
from reflex.vars.base import (
- LiteralNoneVar,
LiteralVar,
- ToOperation,
Var,
)
from reflex.vars.function import (
@@ -468,33 +465,97 @@ prevent_default = EventChain(events=[], args_spec=empty_event).prevent_default
T = TypeVar("T")
+U = TypeVar("U")
-def identity_event(event_type: Type[T]) -> Callable[[Var[T]], Tuple[Var[T]]]:
+# def identity_event(event_type: Type[T]) -> Callable[[Var[T]], Tuple[Var[T]]]:
+# """A helper function that returns the input event as output.
+
+# Args:
+# event_type: The type of the event.
+
+# Returns:
+# A function that returns the input event as output.
+# """
+
+# def inner(ev: Var[T]) -> Tuple[Var[T]]:
+# return (ev,)
+
+# inner.__signature__ = inspect.signature(inner).replace( # type: ignore
+# parameters=[
+# inspect.Parameter(
+# "ev",
+# kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
+# annotation=Var[event_type],
+# )
+# ],
+# return_annotation=Tuple[Var[event_type]],
+# )
+# inner.__annotations__["ev"] = Var[event_type]
+# inner.__annotations__["return"] = Tuple[Var[event_type]]
+
+# return inner
+
+
+class IdentityEventReturn(Generic[T], Protocol):
+ """Protocol for an identity event return."""
+
+ def __call__(self, *values: Var[T]) -> Tuple[Var[T], ...]:
+ """Return the input values.
+
+ Args:
+ *values: The values to return.
+
+ Returns:
+ The input values.
+ """
+ return values
+
+
+@overload
+def identity_event(event_type: Type[T], /) -> Callable[[Var[T]], Tuple[Var[T]]]: ... # type: ignore
+
+
+@overload
+def identity_event(
+ event_type_1: Type[T], event_type2: Type[U], /
+) -> Callable[[Var[T], Var[U]], Tuple[Var[T], Var[U]]]: ...
+
+
+@overload
+def identity_event(*event_types: Type[T]) -> IdentityEventReturn[T]: ...
+
+
+def identity_event(*event_types: Type[T]) -> IdentityEventReturn[T]: # type: ignore
"""A helper function that returns the input event as output.
Args:
- event_type: The type of the event.
+ *event_types: The types of the events.
Returns:
A function that returns the input event as output.
"""
- def inner(ev: Var[T]) -> Tuple[Var[T]]:
- return (ev,)
+ def inner(*values: Var[T]) -> Tuple[Var[T], ...]:
+ return values
+
+ inner_type = tuple(Var[event_type] for event_type in event_types)
+ return_annotation = Tuple[inner_type] # type: ignore
inner.__signature__ = inspect.signature(inner).replace( # type: ignore
parameters=[
inspect.Parameter(
- "ev",
+ f"ev_{i}",
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
annotation=Var[event_type],
)
+ for i, event_type in enumerate(event_types)
],
- return_annotation=Tuple[Var[event_type]],
+ return_annotation=return_annotation,
)
- inner.__annotations__["ev"] = Var[event_type]
- inner.__annotations__["return"] = Tuple[Var[event_type]]
+ for i, event_type in enumerate(event_types):
+ inner.__annotations__[f"ev_{i}"] = Var[event_type]
+ inner.__annotations__["return"] = return_annotation
return inner
@@ -1254,7 +1315,7 @@ def get_fn_signature(fn: Callable) -> inspect.Signature:
return signature.replace(parameters=(new_param, *signature.parameters.values()))
-class EventVar(ObjectVar):
+class EventVar(ObjectVar, python_types=EventSpec):
"""Base class for event vars."""
@@ -1315,7 +1376,7 @@ class LiteralEventVar(VarOperationCall, LiteralVar, EventVar):
)
-class EventChainVar(FunctionVar):
+class EventChainVar(FunctionVar, python_types=EventChain):
"""Base class for event chain vars."""
@@ -1384,32 +1445,6 @@ class LiteralEventChainVar(ArgsFunctionOperation, LiteralVar, EventChainVar):
)
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToEventVarOperation(ToOperation, EventVar):
- """Result of a cast to an event var."""
-
- _original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
-
- _default_var_type: ClassVar[Type] = EventSpec
-
-
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToEventChainVarOperation(ToOperation, EventChainVar):
- """Result of a cast to an event chain var."""
-
- _original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
-
- _default_var_type: ClassVar[Type] = EventChain
-
-
G = ParamSpec("G")
IndividualEventType = Union[EventSpec, EventHandler, Callable[G, Any], Var[Any]]
@@ -1537,8 +1572,6 @@ class EventNamespace(types.SimpleNamespace):
LiteralEventVar = LiteralEventVar
EventChainVar = EventChainVar
LiteralEventChainVar = LiteralEventChainVar
- ToEventVarOperation = ToEventVarOperation
- ToEventChainVarOperation = ToEventChainVarOperation
EventType = EventType
__call__ = staticmethod(event_handler)
diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py
index 0c11deb85..164790fe5 100644
--- a/reflex/experimental/__init__.py
+++ b/reflex/experimental/__init__.py
@@ -2,6 +2,7 @@
from types import SimpleNamespace
+from reflex.components.datadisplay.shiki_code_block import code_block as code_block
from reflex.components.props import PropsBase
from reflex.components.radix.themes.components.progress import progress as progress
from reflex.components.sonner.toast import toast as toast
@@ -67,4 +68,5 @@ _x = ExperimentalNamespace(
layout=layout,
PropsBase=PropsBase,
run_in_thread=run_in_thread,
+ code_block=code_block,
)
diff --git a/reflex/experimental/layout.pyi b/reflex/experimental/layout.pyi
index e4c82b351..dcdac5b5d 100644
--- a/reflex/experimental/layout.pyi
+++ b/reflex/experimental/layout.pyi
@@ -129,7 +129,7 @@ class DrawerSidebar(DrawerRoot):
on_mouse_out: Optional[EventType[[]]] = None,
on_mouse_over: Optional[EventType[[]]] = None,
on_mouse_up: Optional[EventType[[]]] = None,
- on_open_change: Optional[EventType] = None,
+ on_open_change: Optional[EventType[bool]] = None,
on_scroll: Optional[EventType[[]]] = None,
on_unmount: Optional[EventType[[]]] = None,
**props,
diff --git a/reflex/istate/storage.py b/reflex/istate/storage.py
new file mode 100644
index 000000000..85e21ffa7
--- /dev/null
+++ b/reflex/istate/storage.py
@@ -0,0 +1,144 @@
+"""Client-side storage classes for reflex state variables."""
+
+from __future__ import annotations
+
+from typing import Any
+
+from reflex.utils import format
+
+
+class ClientStorageBase:
+ """Base class for client-side storage."""
+
+ def options(self) -> dict[str, Any]:
+ """Get the options for the storage.
+
+ Returns:
+ All set options for the storage (not None).
+ """
+ return {
+ format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
+ }
+
+
+class Cookie(ClientStorageBase, str):
+ """Represents a state Var that is stored as a cookie in the browser."""
+
+ name: str | None
+ path: str
+ max_age: int | None
+ domain: str | None
+ secure: bool | None
+ same_site: str
+
+ def __new__(
+ cls,
+ object: Any = "",
+ encoding: str | None = None,
+ errors: str | None = None,
+ /,
+ name: str | None = None,
+ path: str = "/",
+ max_age: int | None = None,
+ domain: str | None = None,
+ secure: bool | None = None,
+ same_site: str = "lax",
+ ):
+ """Create a client-side Cookie (str).
+
+ Args:
+ object: The initial object.
+ encoding: The encoding to use.
+ errors: The error handling scheme to use.
+ name: The name of the cookie on the client side.
+ path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
+ max_age: Relative max age of the cookie in seconds from when the client receives it.
+ domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
+ secure: Is the cookie only accessible through HTTPS?
+ same_site: Whether the cookie is sent with third party requests.
+ One of (true|false|none|lax|strict)
+
+ Returns:
+ The client-side Cookie object.
+
+ Note: expires (absolute Date) is not supported at this time.
+ """
+ if encoding or errors:
+ inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
+ else:
+ inst = super().__new__(cls, object)
+ inst.name = name
+ inst.path = path
+ inst.max_age = max_age
+ inst.domain = domain
+ inst.secure = secure
+ inst.same_site = same_site
+ return inst
+
+
+class LocalStorage(ClientStorageBase, str):
+ """Represents a state Var that is stored in localStorage in the browser."""
+
+ name: str | None
+ sync: bool = False
+
+ def __new__(
+ cls,
+ object: Any = "",
+ encoding: str | None = None,
+ errors: str | None = None,
+ /,
+ name: str | None = None,
+ sync: bool = False,
+ ) -> "LocalStorage":
+ """Create a client-side localStorage (str).
+
+ Args:
+ object: The initial object.
+ encoding: The encoding to use.
+ errors: The error handling scheme to use.
+ name: The name of the storage key on the client side.
+ sync: Whether changes should be propagated to other tabs.
+
+ Returns:
+ The client-side localStorage object.
+ """
+ if encoding or errors:
+ inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
+ else:
+ inst = super().__new__(cls, object)
+ inst.name = name
+ inst.sync = sync
+ return inst
+
+
+class SessionStorage(ClientStorageBase, str):
+ """Represents a state Var that is stored in sessionStorage in the browser."""
+
+ name: str | None
+
+ def __new__(
+ cls,
+ object: Any = "",
+ encoding: str | None = None,
+ errors: str | None = None,
+ /,
+ name: str | None = None,
+ ) -> "SessionStorage":
+ """Create a client-side sessionStorage (str).
+
+ Args:
+ object: The initial object.
+ encoding: The encoding to use.
+ errors: The error handling scheme to use
+ name: The name of the storage on the client side
+
+ Returns:
+ The client-side sessionStorage object.
+ """
+ if encoding or errors:
+ inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
+ else:
+ inst = super().__new__(cls, object)
+ inst.name = name
+ return inst
diff --git a/reflex/state.py b/reflex/state.py
index 0634ad5b7..208c23a0f 100644
--- a/reflex/state.py
+++ b/reflex/state.py
@@ -30,6 +30,7 @@ from typing import (
Set,
Tuple,
Type,
+ TypeVar,
Union,
cast,
get_args,
@@ -39,8 +40,12 @@ from typing import (
from sqlalchemy.orm import DeclarativeBase
from typing_extensions import Self
+from reflex import event
from reflex.config import get_config
from reflex.istate.data import RouterData
+from reflex.istate.storage import (
+ ClientStorageBase,
+)
from reflex.vars.base import (
ComputedVar,
DynamicRouteVar,
@@ -75,6 +80,7 @@ from reflex.utils import console, format, path_ops, prerequisites, types
from reflex.utils.exceptions import (
ComputedVarShadowsBaseVars,
ComputedVarShadowsStateVar,
+ DynamicComponentInvalidSignature,
DynamicRouteArgShadowsStateVar,
EventHandlerShadowsBuiltInStateMethod,
ImmutableStateError,
@@ -2091,10 +2097,56 @@ class State(BaseState):
is_hydrated: bool = False
+T = TypeVar("T", bound=BaseState)
+
+
+def dynamic(func: Callable[[T], Component]):
+ """Create a dynamically generated components from a state class.
+
+ Args:
+ func: The function to generate the component.
+
+ Returns:
+ The dynamically generated component.
+
+ Raises:
+ DynamicComponentInvalidSignature: If the function does not have exactly one parameter.
+ DynamicComponentInvalidSignature: If the function does not have a type hint for the state class.
+ """
+ number_of_parameters = len(inspect.signature(func).parameters)
+
+ func_signature = get_type_hints(func)
+
+ if "return" in func_signature:
+ func_signature.pop("return")
+
+ values = list(func_signature.values())
+
+ if number_of_parameters != 1:
+ raise DynamicComponentInvalidSignature(
+ "The function must have exactly one parameter, which is the state class."
+ )
+
+ if len(values) != 1:
+ raise DynamicComponentInvalidSignature(
+ "You must provide a type hint for the state class in the function."
+ )
+
+ state_class: Type[T] = values[0]
+
+ def wrapper() -> Component:
+ from reflex.components.base.fragment import fragment
+
+ return fragment(state_class._evaluate(lambda state: func(state)))
+
+ return wrapper
+
+
class FrontendEventExceptionState(State):
"""Substate for handling frontend exceptions."""
- def handle_frontend_exception(self, stack: str) -> None:
+ @event
+ def handle_frontend_exception(self, stack: str, component_stack: str) -> None:
"""Handle frontend exceptions.
If a frontend exception handler is provided, it will be called.
@@ -2102,6 +2154,7 @@ class FrontendEventExceptionState(State):
Args:
stack: The stack trace of the exception.
+ component_stack: The stack trace of the component where the exception occurred.
"""
app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP)
@@ -3346,143 +3399,6 @@ def get_state_manager() -> StateManager:
return app.state_manager
-class ClientStorageBase:
- """Base class for client-side storage."""
-
- def options(self) -> dict[str, Any]:
- """Get the options for the storage.
-
- Returns:
- All set options for the storage (not None).
- """
- return {
- format.to_camel_case(k): v for k, v in vars(self).items() if v is not None
- }
-
-
-class Cookie(ClientStorageBase, str):
- """Represents a state Var that is stored as a cookie in the browser."""
-
- name: str | None
- path: str
- max_age: int | None
- domain: str | None
- secure: bool | None
- same_site: str
-
- def __new__(
- cls,
- object: Any = "",
- encoding: str | None = None,
- errors: str | None = None,
- /,
- name: str | None = None,
- path: str = "/",
- max_age: int | None = None,
- domain: str | None = None,
- secure: bool | None = None,
- same_site: str = "lax",
- ):
- """Create a client-side Cookie (str).
-
- Args:
- object: The initial object.
- encoding: The encoding to use.
- errors: The error handling scheme to use.
- name: The name of the cookie on the client side.
- path: Cookie path. Use / as the path if the cookie should be accessible on all pages.
- max_age: Relative max age of the cookie in seconds from when the client receives it.
- domain: Domain for the cookie (sub.domain.com or .allsubdomains.com).
- secure: Is the cookie only accessible through HTTPS?
- same_site: Whether the cookie is sent with third party requests.
- One of (true|false|none|lax|strict)
-
- Returns:
- The client-side Cookie object.
-
- Note: expires (absolute Date) is not supported at this time.
- """
- if encoding or errors:
- inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
- else:
- inst = super().__new__(cls, object)
- inst.name = name
- inst.path = path
- inst.max_age = max_age
- inst.domain = domain
- inst.secure = secure
- inst.same_site = same_site
- return inst
-
-
-class LocalStorage(ClientStorageBase, str):
- """Represents a state Var that is stored in localStorage in the browser."""
-
- name: str | None
- sync: bool = False
-
- def __new__(
- cls,
- object: Any = "",
- encoding: str | None = None,
- errors: str | None = None,
- /,
- name: str | None = None,
- sync: bool = False,
- ) -> "LocalStorage":
- """Create a client-side localStorage (str).
-
- Args:
- object: The initial object.
- encoding: The encoding to use.
- errors: The error handling scheme to use.
- name: The name of the storage key on the client side.
- sync: Whether changes should be propagated to other tabs.
-
- Returns:
- The client-side localStorage object.
- """
- if encoding or errors:
- inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
- else:
- inst = super().__new__(cls, object)
- inst.name = name
- inst.sync = sync
- return inst
-
-
-class SessionStorage(ClientStorageBase, str):
- """Represents a state Var that is stored in sessionStorage in the browser."""
-
- name: str | None
-
- def __new__(
- cls,
- object: Any = "",
- encoding: str | None = None,
- errors: str | None = None,
- /,
- name: str | None = None,
- ) -> "SessionStorage":
- """Create a client-side sessionStorage (str).
-
- Args:
- object: The initial object.
- encoding: The encoding to use.
- errors: The error handling scheme to use
- name: The name of the storage on the client side
-
- Returns:
- The client-side sessionStorage object.
- """
- if encoding or errors:
- inst = super().__new__(cls, object, encoding or "utf-8", errors or "strict")
- else:
- inst = super().__new__(cls, object)
- inst.name = name
- return inst
-
-
class MutableProxy(wrapt.ObjectProxy):
"""A proxy for a mutable object that tracks changes."""
diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py
index cd3d108b4..f16338513 100644
--- a/reflex/utils/exceptions.py
+++ b/reflex/utils/exceptions.py
@@ -139,3 +139,7 @@ class StateSchemaMismatchError(ReflexError, TypeError):
class EnvironmentVarValueError(ReflexError, ValueError):
"""Raised when an environment variable is set to an invalid value."""
+
+
+class DynamicComponentInvalidSignature(ReflexError, TypeError):
+ """Raised when a dynamic component has an invalid signature."""
diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py
index f5521c91f..bdc9be4ae 100644
--- a/reflex/utils/exec.py
+++ b/reflex/utils/exec.py
@@ -337,8 +337,8 @@ def run_uvicorn_backend_prod(host, port, loglevel):
app_module = get_app_module()
- RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split()
- RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split()
+ RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split()
+ RUN_BACKEND_PROD_WINDOWS = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split()
command = (
[
*RUN_BACKEND_PROD_WINDOWS,
diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py
index fd76576b9..1fc17341b 100644
--- a/reflex/utils/pyi_generator.py
+++ b/reflex/utils/pyi_generator.py
@@ -16,7 +16,7 @@ from itertools import chain
from multiprocessing import Pool, cpu_count
from pathlib import Path
from types import ModuleType, SimpleNamespace
-from typing import Any, Callable, Iterable, Type, get_args
+from typing import Any, Callable, Iterable, Type, get_args, get_origin
from reflex.components.component import Component
from reflex.utils import types as rx_types
@@ -214,7 +214,9 @@ def _get_type_hint(value, type_hint_globals, is_optional=True) -> str:
return res
-def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
+def _generate_imports(
+ typing_imports: Iterable[str],
+) -> list[ast.ImportFrom | ast.Import]:
"""Generate the import statements for the stub file.
Args:
@@ -228,6 +230,7 @@ def _generate_imports(typing_imports: Iterable[str]) -> list[ast.ImportFrom]:
ast.ImportFrom(module=name, names=[ast.alias(name=val) for val in values])
for name, values in DEFAULT_IMPORTS.items()
],
+ ast.Import([ast.alias("reflex")]),
]
@@ -372,6 +375,64 @@ def _extract_class_props_as_ast_nodes(
return kwargs
+def type_to_ast(typ, cls: type) -> ast.AST:
+ """Converts any type annotation into its AST representation.
+ Handles nested generic types, unions, etc.
+
+ Args:
+ typ: The type annotation to convert.
+ cls: The class where the type annotation is used.
+
+ Returns:
+ The AST representation of the type annotation.
+ """
+ if typ is type(None):
+ return ast.Name(id="None")
+
+ origin = get_origin(typ)
+
+ # Handle plain types (int, str, custom classes, etc.)
+ if origin is None:
+ if hasattr(typ, "__name__"):
+ if typ.__module__.startswith("reflex."):
+ typ_parts = typ.__module__.split(".")
+ cls_parts = cls.__module__.split(".")
+
+ zipped = list(zip(typ_parts, cls_parts, strict=False))
+
+ if all(a == b for a, b in zipped) and len(typ_parts) == len(cls_parts):
+ return ast.Name(id=typ.__name__)
+
+ return ast.Name(id=typ.__module__ + "." + typ.__name__)
+ return ast.Name(id=typ.__name__)
+ elif hasattr(typ, "_name"):
+ return ast.Name(id=typ._name)
+ return ast.Name(id=str(typ))
+
+ # Get the base type name (List, Dict, Optional, etc.)
+ base_name = origin._name if hasattr(origin, "_name") else origin.__name__
+
+ # Get type arguments
+ args = get_args(typ)
+
+ # Handle empty type arguments
+ if not args:
+ return ast.Name(id=base_name)
+
+ # Convert all type arguments recursively
+ arg_nodes = [type_to_ast(arg, cls) for arg in args]
+
+ # Special case for single-argument types (like List[T] or Optional[T])
+ if len(arg_nodes) == 1:
+ slice_value = arg_nodes[0]
+ else:
+ slice_value = ast.Tuple(elts=arg_nodes, ctx=ast.Load())
+
+ return ast.Subscript(
+ value=ast.Name(id=base_name), slice=ast.Index(value=slice_value), ctx=ast.Load()
+ )
+
+
def _get_parent_imports(func):
_imports = {"reflex.vars": ["Var"]}
for type_hint in inspect.get_annotations(func).values():
@@ -430,13 +491,40 @@ def _generate_component_create_functiondef(
def figure_out_return_type(annotation: Any):
if inspect.isclass(annotation) and issubclass(annotation, inspect._empty):
return ast.Name(id="Optional[EventType]")
+
+ if not isinstance(annotation, str) and get_origin(annotation) is tuple:
+ arguments = get_args(annotation)
+
+ arguments_without_var = [
+ get_args(argument)[0] if get_origin(argument) == Var else argument
+ for argument in arguments
+ ]
+
+ # Convert each argument type to its AST representation
+ type_args = [type_to_ast(arg, cls=clz) for arg in arguments_without_var]
+
+ # Join the type arguments with commas for EventType
+ args_str = ", ".join(ast.unparse(arg) for arg in type_args)
+
+ # Create EventType using the joined string
+ event_type = ast.Name(id=f"EventType[{args_str}]")
+
+ # Wrap in Optional
+ optional_type = ast.Subscript(
+ value=ast.Name(id="Optional"),
+ slice=ast.Index(value=event_type),
+ ctx=ast.Load(),
+ )
+
+ return ast.Name(id=ast.unparse(optional_type))
+
if isinstance(annotation, str) and annotation.startswith("Tuple["):
inside_of_tuple = annotation.removeprefix("Tuple[").removesuffix("]")
if inside_of_tuple == "()":
return ast.Name(id="Optional[EventType[[]]]")
- arguments: list[str] = [""]
+ arguments = [""]
bracket_count = 0
diff --git a/reflex/vars/base.py b/reflex/vars/base.py
index cf02863c3..6df8eec6f 100644
--- a/reflex/vars/base.py
+++ b/reflex/vars/base.py
@@ -19,6 +19,7 @@ from typing import (
TYPE_CHECKING,
Any,
Callable,
+ ClassVar,
Dict,
FrozenSet,
Generic,
@@ -37,7 +38,13 @@ from typing import (
overload,
)
-from typing_extensions import ParamSpec, TypeGuard, deprecated, get_type_hints, override
+from typing_extensions import (
+ ParamSpec,
+ TypeGuard,
+ deprecated,
+ get_type_hints,
+ override,
+)
from reflex import constants
from reflex.base import Base
@@ -61,15 +68,13 @@ from reflex.utils.types import GenericType, Self, get_origin, has_args, unionize
if TYPE_CHECKING:
from reflex.state import BaseState
- from .function import FunctionVar, ToFunctionOperation
+ from .function import FunctionVar
from .number import (
BooleanVar,
NumberVar,
- ToBooleanVarOperation,
- ToNumberVarOperation,
)
- from .object import ObjectVar, ToObjectOperation
- from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
+ from .object import ObjectVar
+ from .sequence import ArrayVar, StringVar
VAR_TYPE = TypeVar("VAR_TYPE", covariant=True)
@@ -78,6 +83,184 @@ OTHER_VAR_TYPE = TypeVar("OTHER_VAR_TYPE")
warnings.filterwarnings("ignore", message="fields may not start with an underscore")
+@dataclasses.dataclass(
+ eq=False,
+ frozen=True,
+)
+class VarSubclassEntry:
+ """Entry for a Var subclass."""
+
+ var_subclass: Type[Var]
+ to_var_subclass: Type[ToOperation]
+ python_types: Tuple[GenericType, ...]
+
+
+_var_subclasses: List[VarSubclassEntry] = []
+_var_literal_subclasses: List[Tuple[Type[LiteralVar], VarSubclassEntry]] = []
+
+
+@dataclasses.dataclass(
+ eq=True,
+ frozen=True,
+)
+class VarData:
+ """Metadata associated with a x."""
+
+ # The name of the enclosing state.
+ state: str = dataclasses.field(default="")
+
+ # The name of the field in the state.
+ field_name: str = dataclasses.field(default="")
+
+ # Imports needed to render this var
+ imports: ImmutableParsedImportDict = dataclasses.field(default_factory=tuple)
+
+ # Hooks that need to be present in the component to render this var
+ hooks: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
+
+ def __init__(
+ self,
+ state: str = "",
+ field_name: str = "",
+ imports: ImportDict | ParsedImportDict | None = None,
+ hooks: dict[str, None] | None = None,
+ ):
+ """Initialize the var data.
+
+ Args:
+ state: The name of the enclosing state.
+ field_name: The name of the field in the state.
+ imports: Imports needed to render this var.
+ hooks: Hooks that need to be present in the component to render this var.
+ """
+ immutable_imports: ImmutableParsedImportDict = tuple(
+ sorted(
+ ((k, tuple(sorted(v))) for k, v in parse_imports(imports or {}).items())
+ )
+ )
+ object.__setattr__(self, "state", state)
+ object.__setattr__(self, "field_name", field_name)
+ object.__setattr__(self, "imports", immutable_imports)
+ object.__setattr__(self, "hooks", tuple(hooks or {}))
+
+ def old_school_imports(self) -> ImportDict:
+ """Return the imports as a mutable dict.
+
+ Returns:
+ The imports as a mutable dict.
+ """
+ return dict((k, list(v)) for k, v in self.imports)
+
+ @classmethod
+ def merge(cls, *others: VarData | None) -> VarData | None:
+ """Merge multiple var data objects.
+
+ Args:
+ *others: The var data objects to merge.
+
+ Returns:
+ The merged var data object.
+ """
+ state = ""
+ field_name = ""
+ _imports = {}
+ hooks = {}
+ for var_data in others:
+ if var_data is None:
+ continue
+ state = state or var_data.state
+ field_name = field_name or var_data.field_name
+ _imports = imports.merge_imports(_imports, var_data.imports)
+ hooks.update(
+ var_data.hooks
+ if isinstance(var_data.hooks, dict)
+ else {k: None for k in var_data.hooks}
+ )
+
+ if state or _imports or hooks or field_name:
+ return VarData(
+ state=state,
+ field_name=field_name,
+ imports=_imports,
+ hooks=hooks,
+ )
+ return None
+
+ def __bool__(self) -> bool:
+ """Check if the var data is non-empty.
+
+ Returns:
+ True if any field is set to a non-default value.
+ """
+ return bool(self.state or self.imports or self.hooks or self.field_name)
+
+ @classmethod
+ def from_state(cls, state: Type[BaseState] | str, field_name: str = "") -> VarData:
+ """Set the state of the var.
+
+ Args:
+ state: The state to set or the full name of the state.
+ field_name: The name of the field in the state. Optional.
+
+ Returns:
+ The var with the set state.
+ """
+ from reflex.utils import format
+
+ state_name = state if isinstance(state, str) else state.get_full_name()
+ return VarData(
+ state=state_name,
+ field_name=field_name,
+ hooks={
+ "const {0} = useContext(StateContexts.{0})".format(
+ format.format_state_name(state_name)
+ ): None
+ },
+ imports={
+ f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
+ "react": [ImportVar(tag="useContext")],
+ },
+ )
+
+
+def _decode_var_immutable(value: str) -> tuple[VarData | None, str]:
+ """Decode the state name from a formatted var.
+
+ Args:
+ value: The value to extract the state name from.
+
+ Returns:
+ The extracted state name and the value without the state name.
+ """
+ var_datas = []
+ if isinstance(value, str):
+ # fast path if there is no encoded VarData
+ if constants.REFLEX_VAR_OPENING_TAG not in value:
+ return None, value
+
+ offset = 0
+
+ # Find all tags.
+ while m := _decode_var_pattern.search(value):
+ start, end = m.span()
+ value = value[:start] + value[end:]
+
+ serialized_data = m.group(1)
+
+ if serialized_data.isnumeric() or (
+ serialized_data[0] == "-" and serialized_data[1:].isnumeric()
+ ):
+ # This is a global immutable var.
+ var = _global_vars[int(serialized_data)]
+ var_data = var._get_all_var_data()
+
+ if var_data is not None:
+ var_datas.append(var_data)
+ offset += end - start
+
+ return VarData.merge(*var_datas) if var_datas else None, value
+
+
@dataclasses.dataclass(
eq=False,
frozen=True,
@@ -151,6 +334,40 @@ class Var(Generic[VAR_TYPE]):
"""
return False
+ def __init_subclass__(
+ cls, python_types: Tuple[GenericType, ...] | GenericType = types.Unset, **kwargs
+ ):
+ """Initialize the subclass.
+
+ Args:
+ python_types: The python types that the var represents.
+ **kwargs: Additional keyword arguments.
+ """
+ super().__init_subclass__(**kwargs)
+
+ if python_types is not types.Unset:
+ python_types = (
+ python_types if isinstance(python_types, tuple) else (python_types,)
+ )
+
+ @dataclasses.dataclass(
+ eq=False,
+ frozen=True,
+ **{"slots": True} if sys.version_info >= (3, 10) else {},
+ )
+ class ToVarOperation(ToOperation, cls):
+ """Base class of converting a var to another var type."""
+
+ _original: Var = dataclasses.field(
+ default=Var(_js_expr="null", _var_type=None),
+ )
+
+ _default_var_type: ClassVar[GenericType] = python_types[0]
+
+ ToVarOperation.__name__ = f'To{cls.__name__.removesuffix("Var")}Operation'
+
+ _var_subclasses.append(VarSubclassEntry(cls, ToVarOperation, python_types))
+
def __post_init__(self):
"""Post-initialize the var."""
# Decode any inline Var markup and apply it to the instance
@@ -331,35 +548,35 @@ class Var(Generic[VAR_TYPE]):
return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._js_expr}"
@overload
- def to(self, output: Type[StringVar]) -> ToStringOperation: ...
+ def to(self, output: Type[StringVar]) -> StringVar: ...
@overload
- def to(self, output: Type[str]) -> ToStringOperation: ...
+ def to(self, output: Type[str]) -> StringVar: ...
@overload
- def to(self, output: Type[BooleanVar]) -> ToBooleanVarOperation: ...
+ def to(self, output: Type[BooleanVar]) -> BooleanVar: ...
@overload
def to(
self, output: Type[NumberVar], var_type: type[int] | type[float] = float
- ) -> ToNumberVarOperation: ...
+ ) -> NumberVar: ...
@overload
def to(
self,
output: Type[ArrayVar],
var_type: type[list] | type[tuple] | type[set] = list,
- ) -> ToArrayOperation: ...
+ ) -> ArrayVar: ...
@overload
def to(
self, output: Type[ObjectVar], var_type: types.GenericType = dict
- ) -> ToObjectOperation: ...
+ ) -> ObjectVar: ...
@overload
def to(
self, output: Type[FunctionVar], var_type: Type[Callable] = Callable
- ) -> ToFunctionOperation: ...
+ ) -> FunctionVar: ...
@overload
def to(
@@ -379,56 +596,26 @@ class Var(Generic[VAR_TYPE]):
output: The output type.
var_type: The type of the var.
- Raises:
- TypeError: If the var_type is not a supported type for the output.
-
Returns:
The converted var.
"""
- from reflex.event import (
- EventChain,
- EventChainVar,
- EventSpec,
- EventVar,
- ToEventChainVarOperation,
- ToEventVarOperation,
- )
-
- from .function import FunctionVar, ToFunctionOperation
- from .number import (
- BooleanVar,
- NumberVar,
- ToBooleanVarOperation,
- ToNumberVarOperation,
- )
- from .object import ObjectVar, ToObjectOperation
- from .sequence import ArrayVar, StringVar, ToArrayOperation, ToStringOperation
+ from .object import ObjectVar
base_type = var_type
if types.is_optional(base_type):
base_type = types.get_args(base_type)[0]
- fixed_type = get_origin(base_type) or base_type
-
fixed_output_type = get_origin(output) or output
# If the first argument is a python type, we map it to the corresponding Var type.
- if fixed_output_type is dict:
- return self.to(ObjectVar, output)
- if fixed_output_type in (list, tuple, set):
- return self.to(ArrayVar, output)
- if fixed_output_type in (int, float):
- return self.to(NumberVar, output)
- if fixed_output_type is str:
- return self.to(StringVar, output)
- if fixed_output_type is bool:
- return self.to(BooleanVar, output)
+ for var_subclass in _var_subclasses[::-1]:
+ if fixed_output_type in var_subclass.python_types:
+ return self.to(var_subclass.var_subclass, output)
+
if fixed_output_type is None:
- return ToNoneOperation.create(self)
- if fixed_output_type is EventSpec:
- return self.to(EventVar, output)
- if fixed_output_type is EventChain:
- return self.to(EventChainVar, output)
+ return get_to_operation(NoneVar).create(self) # type: ignore
+
+ # Handle fixed_output_type being Base or a dataclass.
try:
if issubclass(fixed_output_type, Base):
return self.to(ObjectVar, output)
@@ -440,57 +627,12 @@ class Var(Generic[VAR_TYPE]):
return self.to(ObjectVar, output)
if inspect.isclass(output):
- if issubclass(output, BooleanVar):
- return ToBooleanVarOperation.create(self)
-
- if issubclass(output, NumberVar):
- if fixed_type is not None:
- if fixed_type in types.UnionTypes:
- inner_types = get_args(base_type)
- if not all(issubclass(t, (int, float)) for t in inner_types):
- raise TypeError(
- f"Unsupported type {var_type} for NumberVar. Must be int or float."
- )
-
- elif not issubclass(fixed_type, (int, float)):
- raise TypeError(
- f"Unsupported type {var_type} for NumberVar. Must be int or float."
- )
- return ToNumberVarOperation.create(self, var_type or float)
-
- if issubclass(output, ArrayVar):
- if fixed_type is not None and not issubclass(
- fixed_type, (list, tuple, set)
- ):
- raise TypeError(
- f"Unsupported type {var_type} for ArrayVar. Must be list, tuple, or set."
+ for var_subclass in _var_subclasses[::-1]:
+ if issubclass(output, var_subclass.var_subclass):
+ to_operation_return = var_subclass.to_var_subclass.create(
+ value=self, _var_type=var_type
)
- return ToArrayOperation.create(self, var_type or list)
-
- if issubclass(output, StringVar):
- return ToStringOperation.create(self, var_type or str)
-
- if issubclass(output, EventVar):
- return ToEventVarOperation.create(self, var_type or EventSpec)
-
- if issubclass(output, EventChainVar):
- return ToEventChainVarOperation.create(self, var_type or EventChain)
-
- if issubclass(output, (ObjectVar, Base)):
- return ToObjectOperation.create(self, var_type or dict)
-
- if issubclass(output, FunctionVar):
- # if fixed_type is not None and not issubclass(fixed_type, Callable):
- # raise TypeError(
- # f"Unsupported type {var_type} for FunctionVar. Must be Callable."
- # )
- return ToFunctionOperation.create(self, var_type or Callable)
-
- if issubclass(output, NoneVar):
- return ToNoneOperation.create(self)
-
- if dataclasses.is_dataclass(output):
- return ToObjectOperation.create(self, var_type or dict)
+ return to_operation_return # type: ignore
# If we can't determine the first argument, we just replace the _var_type.
if not issubclass(output, Var) or var_type is None:
@@ -508,6 +650,18 @@ class Var(Generic[VAR_TYPE]):
return self
+ @overload
+ def guess_type(self: Var[str]) -> StringVar: ...
+
+ @overload
+ def guess_type(self: Var[bool]) -> BooleanVar: ...
+
+ @overload
+ def guess_type(self: Var[int] | Var[float] | Var[int | float]) -> NumberVar: ...
+
+ @overload
+ def guess_type(self) -> Self: ...
+
def guess_type(self) -> Var:
"""Guesses the type of the variable based on its `_var_type` attribute.
@@ -517,11 +671,8 @@ class Var(Generic[VAR_TYPE]):
Raises:
TypeError: If the type is not supported for guessing.
"""
- from reflex.event import EventChain, EventChainVar, EventSpec, EventVar
-
- from .number import BooleanVar, NumberVar
+ from .number import NumberVar
from .object import ObjectVar
- from .sequence import ArrayVar, StringVar
var_type = self._var_type
if var_type is None:
@@ -558,20 +709,13 @@ class Var(Generic[VAR_TYPE]):
if not inspect.isclass(fixed_type):
raise TypeError(f"Unsupported type {var_type} for guess_type.")
- if issubclass(fixed_type, bool):
- return self.to(BooleanVar, self._var_type)
- if issubclass(fixed_type, (int, float)):
- return self.to(NumberVar, self._var_type)
- if issubclass(fixed_type, dict):
- return self.to(ObjectVar, self._var_type)
- if issubclass(fixed_type, (list, tuple, set)):
- return self.to(ArrayVar, self._var_type)
- if issubclass(fixed_type, str):
- return self.to(StringVar, self._var_type)
- if issubclass(fixed_type, EventSpec):
- return self.to(EventVar, self._var_type)
- if issubclass(fixed_type, EventChain):
- return self.to(EventChainVar, self._var_type)
+ if fixed_type is None:
+ return self.to(None)
+
+ for var_subclass in _var_subclasses[::-1]:
+ if issubclass(fixed_type, var_subclass.python_types):
+ return self.to(var_subclass.var_subclass, self._var_type)
+
try:
if issubclass(fixed_type, Base):
return self.to(ObjectVar, self._var_type)
@@ -782,16 +926,23 @@ class Var(Generic[VAR_TYPE]):
"""
return ~self.bool()
- def to_string(self):
+ def to_string(self, use_json: bool = True) -> StringVar:
"""Convert the var to a string.
+ Args:
+ use_json: Whether to use JSON stringify. If False, uses Object.prototype.toString.
+
Returns:
The string var.
"""
- from .function import JSON_STRINGIFY
+ from .function import JSON_STRINGIFY, PROTOTYPE_TO_STRING
from .sequence import StringVar
- return JSON_STRINGIFY.call(self).to(StringVar)
+ return (
+ JSON_STRINGIFY.call(self).to(StringVar)
+ if use_json
+ else PROTOTYPE_TO_STRING.call(self).to(StringVar)
+ )
def as_ref(self) -> Var:
"""Get a reference to the var.
@@ -1017,9 +1168,129 @@ class Var(Generic[VAR_TYPE]):
OUTPUT = TypeVar("OUTPUT", bound=Var)
+class ToOperation:
+ """A var operation that converts a var to another type."""
+
+ def __getattr__(self, name: str) -> Any:
+ """Get an attribute of the var.
+
+ Args:
+ name: The name of the attribute.
+
+ Returns:
+ The attribute of the var.
+ """
+ from .object import ObjectVar
+
+ if isinstance(self, ObjectVar) and name != "_js_expr":
+ return ObjectVar.__getattr__(self, name)
+ return getattr(self._original, name)
+
+ def __post_init__(self):
+ """Post initialization."""
+ object.__delattr__(self, "_js_expr")
+
+ def __hash__(self) -> int:
+ """Calculate the hash value of the object.
+
+ Returns:
+ int: The hash value of the object.
+ """
+ return hash(self._original)
+
+ def _get_all_var_data(self) -> VarData | None:
+ """Get all the var data.
+
+ Returns:
+ The var data.
+ """
+ return VarData.merge(
+ self._original._get_all_var_data(),
+ self._var_data, # type: ignore
+ )
+
+ @classmethod
+ def create(
+ cls,
+ value: Var,
+ _var_type: GenericType | None = None,
+ _var_data: VarData | None = None,
+ ):
+ """Create a ToOperation.
+
+ Args:
+ value: The value of the var.
+ _var_type: The type of the Var.
+ _var_data: Additional hooks and imports associated with the Var.
+
+ Returns:
+ The ToOperation.
+ """
+ return cls(
+ _js_expr="", # type: ignore
+ _var_data=_var_data, # type: ignore
+ _var_type=_var_type or cls._default_var_type, # type: ignore
+ _original=value, # type: ignore
+ )
+
+
class LiteralVar(Var):
"""Base class for immutable literal vars."""
+ def __init_subclass__(cls, **kwargs):
+ """Initialize the subclass.
+
+ Args:
+ **kwargs: Additional keyword arguments.
+
+ Raises:
+ TypeError: If the LiteralVar subclass does not have a corresponding Var subclass.
+ """
+ super().__init_subclass__(**kwargs)
+
+ bases = cls.__bases__
+
+ bases_normalized = [
+ base if inspect.isclass(base) else get_origin(base) for base in bases
+ ]
+
+ possible_bases = [
+ base
+ for base in bases_normalized
+ if issubclass(base, Var) and base != LiteralVar
+ ]
+
+ if not possible_bases:
+ raise TypeError(
+ f"LiteralVar subclass {cls} must have a base class that is a subclass of Var and not LiteralVar."
+ )
+
+ var_subclasses = [
+ var_subclass
+ for var_subclass in _var_subclasses
+ if var_subclass.var_subclass in possible_bases
+ ]
+
+ if not var_subclasses:
+ raise TypeError(
+ f"LiteralVar {cls} must have a base class annotated with `python_types`."
+ )
+
+ if len(var_subclasses) != 1:
+ raise TypeError(
+ f"LiteralVar {cls} must have exactly one base class annotated with `python_types`."
+ )
+
+ var_subclass = var_subclasses[0]
+
+ # Remove the old subclass, happens because __init_subclass__ is called twice
+ # for each subclass. This is because of __slots__ in dataclasses.
+ for var_literal_subclass in list(_var_literal_subclasses):
+ if var_literal_subclass[1] is var_subclass:
+ _var_literal_subclasses.remove(var_literal_subclass)
+
+ _var_literal_subclasses.append((cls, var_subclass))
+
@classmethod
def create(
cls,
@@ -1038,50 +1309,21 @@ class LiteralVar(Var):
Raises:
TypeError: If the value is not a supported type for LiteralVar.
"""
- from .number import LiteralBooleanVar, LiteralNumberVar
from .object import LiteralObjectVar
- from .sequence import LiteralArrayVar, LiteralStringVar
+ from .sequence import LiteralStringVar
if isinstance(value, Var):
if _var_data is None:
return value
return value._replace(merge_var_data=_var_data)
- if isinstance(value, str):
- return LiteralStringVar.create(value, _var_data=_var_data)
+ for literal_subclass, var_subclass in _var_literal_subclasses[::-1]:
+ if isinstance(value, var_subclass.python_types):
+ return literal_subclass.create(value, _var_data=_var_data)
- if isinstance(value, bool):
- return LiteralBooleanVar.create(value, _var_data=_var_data)
-
- if isinstance(value, (int, float)):
- return LiteralNumberVar.create(value, _var_data=_var_data)
-
- if isinstance(value, dict):
- return LiteralObjectVar.create(value, _var_data=_var_data)
-
- if isinstance(value, (list, tuple, set)):
- return LiteralArrayVar.create(value, _var_data=_var_data)
-
- if value is None:
- return LiteralNoneVar.create(_var_data=_var_data)
-
- from reflex.event import (
- EventChain,
- EventHandler,
- EventSpec,
- LiteralEventChainVar,
- LiteralEventVar,
- )
+ from reflex.event import EventHandler
from reflex.utils.format import get_event_handler_parts
- from .object import LiteralObjectVar
-
- if isinstance(value, EventSpec):
- return LiteralEventVar.create(value, _var_data=_var_data)
-
- if isinstance(value, EventChain):
- return LiteralEventChainVar.create(value, _var_data=_var_data)
-
if isinstance(value, EventHandler):
return Var(_js_expr=".".join(filter(None, get_event_handler_parts(value))))
@@ -1155,6 +1397,22 @@ def serialize_literal(value: LiteralVar):
return value._var_value
+def get_python_literal(value: Union[LiteralVar, Any]) -> Any | None:
+ """Get the Python literal value.
+
+ Args:
+ value: The value to get the Python literal value of.
+
+ Returns:
+ The Python literal value.
+ """
+ if isinstance(value, LiteralVar):
+ return value._var_value
+ if isinstance(value, Var):
+ return None
+ return value
+
+
P = ParamSpec("P")
T = TypeVar("T")
@@ -1205,6 +1463,12 @@ def var_operation(
) -> Callable[P, ObjectVar[OBJECT_TYPE]]: ...
+@overload
+def var_operation(
+ func: Callable[P, CustomVarOperationReturn[T]],
+) -> Callable[P, Var[T]]: ...
+
+
def var_operation(
func: Callable[P, CustomVarOperationReturn[T]],
) -> Callable[P, Var[T]]:
@@ -1237,6 +1501,7 @@ def var_operation(
}
return CustomVarOperation.create(
+ name=func.__name__,
args=tuple(list(args_vars.items()) + list(kwargs_vars.items())),
return_var=func(*args_vars.values(), **kwargs_vars), # type: ignore
).guess_type()
@@ -2062,6 +2327,8 @@ def var_operation_return(
class CustomVarOperation(CachedVarOperation, Var[T]):
"""Base class for custom var operations."""
+ _name: str = dataclasses.field(default="")
+
_args: Tuple[Tuple[str, Var], ...] = dataclasses.field(default_factory=tuple)
_return: CustomVarOperationReturn[T] = dataclasses.field(
@@ -2096,6 +2363,7 @@ class CustomVarOperation(CachedVarOperation, Var[T]):
@classmethod
def create(
cls,
+ name: str,
args: Tuple[Tuple[str, Var], ...],
return_var: CustomVarOperationReturn[T],
_var_data: VarData | None = None,
@@ -2103,6 +2371,7 @@ class CustomVarOperation(CachedVarOperation, Var[T]):
"""Create a CustomVarOperation.
Args:
+ name: The name of the operation.
args: The arguments to the operation.
return_var: The return var.
_var_data: Additional hooks and imports associated with the Var.
@@ -2114,12 +2383,13 @@ class CustomVarOperation(CachedVarOperation, Var[T]):
_js_expr="",
_var_type=return_var._var_type,
_var_data=_var_data,
+ _name=name,
_args=args,
_return=return_var,
)
-class NoneVar(Var[None]):
+class NoneVar(Var[None], python_types=type(None)):
"""A var representing None."""
@@ -2144,11 +2414,13 @@ class LiteralNoneVar(LiteralVar, NoneVar):
@classmethod
def create(
cls,
+ value: None = None,
_var_data: VarData | None = None,
) -> LiteralNoneVar:
"""Create a var from a value.
Args:
+ value: The value of the var. Must be None. Existed for compatibility with LiteralVar.
_var_data: Additional hooks and imports associated with the Var.
Returns:
@@ -2161,48 +2433,26 @@ class LiteralNoneVar(LiteralVar, NoneVar):
)
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToNoneOperation(CachedVarOperation, NoneVar):
- """A var operation that converts a var to None."""
+def get_to_operation(var_subclass: Type[Var]) -> Type[ToOperation]:
+ """Get the ToOperation class for a given Var subclass.
- _original_var: Var = dataclasses.field(
- default_factory=lambda: LiteralNoneVar.create()
- )
+ Args:
+ var_subclass: The Var subclass.
- @cached_property_no_lock
- def _cached_var_name(self) -> str:
- """Get the cached var name.
+ Returns:
+ The ToOperation class.
- Returns:
- The cached var name.
- """
- return str(self._original_var)
-
- @classmethod
- def create(
- cls,
- var: Var,
- _var_data: VarData | None = None,
- ) -> ToNoneOperation:
- """Create a ToNoneOperation.
-
- Args:
- var: The var to convert to None.
- _var_data: Additional hooks and imports associated with the Var.
-
- Returns:
- The ToNoneOperation.
- """
- return ToNoneOperation(
- _js_expr="",
- _var_type=None,
- _var_data=_var_data,
- _original_var=var,
- )
+ Raises:
+ ValueError: If the ToOperation class cannot be found.
+ """
+ possible_classes = [
+ saved_var_subclass.to_var_subclass
+ for saved_var_subclass in _var_subclasses
+ if saved_var_subclass.var_subclass is var_subclass
+ ]
+ if not possible_classes:
+ raise ValueError(f"Could not find ToOperation for {var_subclass}.")
+ return possible_classes[0]
@dataclasses.dataclass(
@@ -2265,68 +2515,6 @@ class StateOperation(CachedVarOperation, Var):
)
-class ToOperation:
- """A var operation that converts a var to another type."""
-
- def __getattr__(self, name: str) -> Any:
- """Get an attribute of the var.
-
- Args:
- name: The name of the attribute.
-
- Returns:
- The attribute of the var.
- """
- return getattr(object.__getattribute__(self, "_original"), name)
-
- def __post_init__(self):
- """Post initialization."""
- object.__delattr__(self, "_js_expr")
-
- def __hash__(self) -> int:
- """Calculate the hash value of the object.
-
- Returns:
- int: The hash value of the object.
- """
- return hash(object.__getattribute__(self, "_original"))
-
- def _get_all_var_data(self) -> VarData | None:
- """Get all the var data.
-
- Returns:
- The var data.
- """
- return VarData.merge(
- object.__getattribute__(self, "_original")._get_all_var_data(),
- self._var_data, # type: ignore
- )
-
- @classmethod
- def create(
- cls,
- value: Var,
- _var_type: GenericType | None = None,
- _var_data: VarData | None = None,
- ):
- """Create a ToOperation.
-
- Args:
- value: The value of the var.
- _var_type: The type of the Var.
- _var_data: Additional hooks and imports associated with the Var.
-
- Returns:
- The ToOperation.
- """
- return cls(
- _js_expr="", # type: ignore
- _var_data=_var_data, # type: ignore
- _var_type=_var_type or cls._default_var_type, # type: ignore
- _original=value, # type: ignore
- )
-
-
def get_uuid_string_var() -> Var:
"""Return a Var that generates a single memoized UUID via .web/utils/state.js.
@@ -2372,168 +2560,6 @@ def get_unique_variable_name() -> str:
return get_unique_variable_name()
-@dataclasses.dataclass(
- eq=True,
- frozen=True,
-)
-class VarData:
- """Metadata associated with a x."""
-
- # The name of the enclosing state.
- state: str = dataclasses.field(default="")
-
- # The name of the field in the state.
- field_name: str = dataclasses.field(default="")
-
- # Imports needed to render this var
- imports: ImmutableParsedImportDict = dataclasses.field(default_factory=tuple)
-
- # Hooks that need to be present in the component to render this var
- hooks: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
-
- def __init__(
- self,
- state: str = "",
- field_name: str = "",
- imports: ImportDict | ParsedImportDict | None = None,
- hooks: dict[str, None] | None = None,
- ):
- """Initialize the var data.
-
- Args:
- state: The name of the enclosing state.
- field_name: The name of the field in the state.
- imports: Imports needed to render this var.
- hooks: Hooks that need to be present in the component to render this var.
- """
- immutable_imports: ImmutableParsedImportDict = tuple(
- sorted(
- ((k, tuple(sorted(v))) for k, v in parse_imports(imports or {}).items())
- )
- )
- object.__setattr__(self, "state", state)
- object.__setattr__(self, "field_name", field_name)
- object.__setattr__(self, "imports", immutable_imports)
- object.__setattr__(self, "hooks", tuple(hooks or {}))
-
- def old_school_imports(self) -> ImportDict:
- """Return the imports as a mutable dict.
-
- Returns:
- The imports as a mutable dict.
- """
- return dict((k, list(v)) for k, v in self.imports)
-
- @classmethod
- def merge(cls, *others: VarData | None) -> VarData | None:
- """Merge multiple var data objects.
-
- Args:
- *others: The var data objects to merge.
-
- Returns:
- The merged var data object.
- """
- state = ""
- field_name = ""
- _imports = {}
- hooks = {}
- for var_data in others:
- if var_data is None:
- continue
- state = state or var_data.state
- field_name = field_name or var_data.field_name
- _imports = imports.merge_imports(_imports, var_data.imports)
- hooks.update(
- var_data.hooks
- if isinstance(var_data.hooks, dict)
- else {k: None for k in var_data.hooks}
- )
-
- if state or _imports or hooks or field_name:
- return VarData(
- state=state,
- field_name=field_name,
- imports=_imports,
- hooks=hooks,
- )
- return None
-
- def __bool__(self) -> bool:
- """Check if the var data is non-empty.
-
- Returns:
- True if any field is set to a non-default value.
- """
- return bool(self.state or self.imports or self.hooks or self.field_name)
-
- @classmethod
- def from_state(cls, state: Type[BaseState] | str, field_name: str = "") -> VarData:
- """Set the state of the var.
-
- Args:
- state: The state to set or the full name of the state.
- field_name: The name of the field in the state. Optional.
-
- Returns:
- The var with the set state.
- """
- from reflex.utils import format
-
- state_name = state if isinstance(state, str) else state.get_full_name()
- return VarData(
- state=state_name,
- field_name=field_name,
- hooks={
- "const {0} = useContext(StateContexts.{0})".format(
- format.format_state_name(state_name)
- ): None
- },
- imports={
- f"/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="StateContexts")],
- "react": [ImportVar(tag="useContext")],
- },
- )
-
-
-def _decode_var_immutable(value: str) -> tuple[VarData | None, str]:
- """Decode the state name from a formatted var.
-
- Args:
- value: The value to extract the state name from.
-
- Returns:
- The extracted state name and the value without the state name.
- """
- var_datas = []
- if isinstance(value, str):
- # fast path if there is no encoded VarData
- if constants.REFLEX_VAR_OPENING_TAG not in value:
- return None, value
-
- offset = 0
-
- # Find all tags.
- while m := _decode_var_pattern.search(value):
- start, end = m.span()
- value = value[:start] + value[end:]
-
- serialized_data = m.group(1)
-
- if serialized_data.isnumeric() or (
- serialized_data[0] == "-" and serialized_data[1:].isnumeric()
- ):
- # This is a global immutable var.
- var = _global_vars[int(serialized_data)]
- var_data = var._get_all_var_data()
-
- if var_data is not None:
- var_datas.append(var_data)
- offset += end - start
-
- return VarData.merge(*var_datas) if var_datas else None, value
-
-
# Compile regex for finding reflex var tags.
_decode_var_pattern_re = (
rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
diff --git a/reflex/vars/function.py b/reflex/vars/function.py
index a512432b9..9d734a458 100644
--- a/reflex/vars/function.py
+++ b/reflex/vars/function.py
@@ -4,21 +4,20 @@ from __future__ import annotations
import dataclasses
import sys
-from typing import Any, Callable, ClassVar, Optional, Tuple, Type, Union
+from typing import Any, Callable, Optional, Tuple, Type, Union
from reflex.utils.types import GenericType
from .base import (
CachedVarOperation,
LiteralVar,
- ToOperation,
Var,
VarData,
cached_property_no_lock,
)
-class FunctionVar(Var[Callable]):
+class FunctionVar(Var[Callable], python_types=Callable):
"""Base class for immutable function vars."""
def __call__(self, *args: Var | Any) -> ArgsFunctionOperation:
@@ -180,17 +179,8 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
)
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToFunctionOperation(ToOperation, FunctionVar):
- """Base class of converting a var to a function."""
-
- _original: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
-
- _default_var_type: ClassVar[GenericType] = Callable
-
-
JSON_STRINGIFY = FunctionStringVar.create("JSON.stringify")
+ARRAY_ISARRAY = FunctionStringVar.create("Array.isArray")
+PROTOTYPE_TO_STRING = FunctionStringVar.create(
+ "((__to_string) => __to_string.toString())"
+)
diff --git a/reflex/vars/number.py b/reflex/vars/number.py
index 0aaa7a068..77c728d13 100644
--- a/reflex/vars/number.py
+++ b/reflex/vars/number.py
@@ -10,7 +10,6 @@ from typing import (
TYPE_CHECKING,
Any,
Callable,
- ClassVar,
NoReturn,
Type,
TypeVar,
@@ -25,9 +24,7 @@ from reflex.utils.types import is_optional
from .base import (
CustomVarOperationReturn,
- LiteralNoneVar,
LiteralVar,
- ToOperation,
Var,
VarData,
unionize,
@@ -58,7 +55,7 @@ def raise_unsupported_operand_types(
)
-class NumberVar(Var[NUMBER_T]):
+class NumberVar(Var[NUMBER_T], python_types=(int, float)):
"""Base class for immutable number vars."""
@overload
@@ -760,7 +757,7 @@ def number_trunc_operation(value: NumberVar):
return var_operation_return(js_expression=f"Math.trunc({value})", var_type=int)
-class BooleanVar(NumberVar[bool]):
+class BooleanVar(NumberVar[bool], python_types=bool):
"""Base class for immutable boolean vars."""
def __invert__(self):
@@ -984,51 +981,6 @@ def boolean_not_operation(value: BooleanVar):
return var_operation_return(js_expression=f"!({value})", var_type=bool)
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class LiteralBooleanVar(LiteralVar, BooleanVar):
- """Base class for immutable literal boolean vars."""
-
- _var_value: bool = dataclasses.field(default=False)
-
- def json(self) -> str:
- """Get the JSON representation of the var.
-
- Returns:
- The JSON representation of the var.
- """
- return "true" if self._var_value else "false"
-
- def __hash__(self) -> int:
- """Calculate the hash value of the object.
-
- Returns:
- int: The hash value of the object.
- """
- return hash((self.__class__.__name__, self._var_value))
-
- @classmethod
- def create(cls, value: bool, _var_data: VarData | None = None):
- """Create the boolean var.
-
- Args:
- value: The value of the var.
- _var_data: Additional hooks and imports associated with the Var.
-
- Returns:
- The boolean var.
- """
- return cls(
- _js_expr="true" if value else "false",
- _var_type=bool,
- _var_data=_var_data,
- _var_value=value,
- )
-
-
@dataclasses.dataclass(
eq=False,
frozen=True,
@@ -1088,36 +1040,55 @@ class LiteralNumberVar(LiteralVar, NumberVar):
)
+@dataclasses.dataclass(
+ eq=False,
+ frozen=True,
+ **{"slots": True} if sys.version_info >= (3, 10) else {},
+)
+class LiteralBooleanVar(LiteralVar, BooleanVar):
+ """Base class for immutable literal boolean vars."""
+
+ _var_value: bool = dataclasses.field(default=False)
+
+ def json(self) -> str:
+ """Get the JSON representation of the var.
+
+ Returns:
+ The JSON representation of the var.
+ """
+ return "true" if self._var_value else "false"
+
+ def __hash__(self) -> int:
+ """Calculate the hash value of the object.
+
+ Returns:
+ int: The hash value of the object.
+ """
+ return hash((self.__class__.__name__, self._var_value))
+
+ @classmethod
+ def create(cls, value: bool, _var_data: VarData | None = None):
+ """Create the boolean var.
+
+ Args:
+ value: The value of the var.
+ _var_data: Additional hooks and imports associated with the Var.
+
+ Returns:
+ The boolean var.
+ """
+ return cls(
+ _js_expr="true" if value else "false",
+ _var_type=bool,
+ _var_data=_var_data,
+ _var_value=value,
+ )
+
+
number_types = Union[NumberVar, int, float]
boolean_types = Union[BooleanVar, bool]
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToNumberVarOperation(ToOperation, NumberVar):
- """Base class for immutable number vars that are the result of a number operation."""
-
- _original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
-
- _default_var_type: ClassVar[Type] = float
-
-
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToBooleanVarOperation(ToOperation, BooleanVar):
- """Base class for immutable boolean vars that are the result of a boolean operation."""
-
- _original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
-
- _default_var_type: ClassVar[Type] = bool
-
-
_IS_TRUE_IMPORT: ImportDict = {
f"/{Dirs.STATE_PATH}": [ImportVar(tag="isTrue")],
}
@@ -1140,8 +1111,12 @@ def boolify(value: Var):
)
+T = TypeVar("T")
+U = TypeVar("U")
+
+
@var_operation
-def ternary_operation(condition: BooleanVar, if_true: Var, if_false: Var):
+def ternary_operation(condition: BooleanVar, if_true: Var[T], if_false: Var[U]):
"""Create a ternary operation.
Args:
@@ -1152,10 +1127,14 @@ def ternary_operation(condition: BooleanVar, if_true: Var, if_false: Var):
Returns:
The ternary operation.
"""
- return var_operation_return(
- js_expression=f"({condition} ? {if_true} : {if_false})",
- var_type=unionize(if_true._var_type, if_false._var_type),
+ type_value: Union[Type[T], Type[U]] = unionize(
+ if_true._var_type, if_false._var_type
)
+ value: CustomVarOperationReturn[Union[T, U]] = var_operation_return(
+ js_expression=f"({condition} ? {if_true} : {if_false})",
+ var_type=type_value,
+ )
+ return value
NUMBER_TYPES = (int, float, NumberVar)
diff --git a/reflex/vars/object.py b/reflex/vars/object.py
index 38add7779..56f3535d8 100644
--- a/reflex/vars/object.py
+++ b/reflex/vars/object.py
@@ -8,7 +8,6 @@ import typing
from inspect import isclass
from typing import (
Any,
- ClassVar,
Dict,
List,
NoReturn,
@@ -27,7 +26,6 @@ from reflex.utils.types import GenericType, get_attribute_access_type, get_origi
from .base import (
CachedVarOperation,
LiteralVar,
- ToOperation,
Var,
VarData,
cached_property_no_lock,
@@ -48,7 +46,7 @@ ARRAY_INNER_TYPE = TypeVar("ARRAY_INNER_TYPE")
OTHER_KEY_TYPE = TypeVar("OTHER_KEY_TYPE")
-class ObjectVar(Var[OBJECT_TYPE]):
+class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
"""Base class for immutable object vars."""
def _key_type(self) -> Type:
@@ -521,34 +519,6 @@ class ObjectItemOperation(CachedVarOperation, Var):
)
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToObjectOperation(ToOperation, ObjectVar):
- """Operation to convert a var to an object."""
-
- _original: Var = dataclasses.field(
- default_factory=lambda: LiteralObjectVar.create({})
- )
-
- _default_var_type: ClassVar[GenericType] = dict
-
- def __getattr__(self, name: str) -> Any:
- """Get an attribute of the var.
-
- Args:
- name: The name of the attribute.
-
- Returns:
- The attribute of the var.
- """
- if name == "_js_expr":
- return self._original._js_expr
- return ObjectVar.__getattr__(self, name)
-
-
@var_operation
def object_has_own_property_operation(object: ObjectVar, key: Var):
"""Check if an object has a key.
diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py
index e3b422f35..39139ce3f 100644
--- a/reflex/vars/sequence.py
+++ b/reflex/vars/sequence.py
@@ -11,7 +11,6 @@ import typing
from typing import (
TYPE_CHECKING,
Any,
- ClassVar,
Dict,
List,
Literal,
@@ -19,27 +18,28 @@ from typing import (
Set,
Tuple,
Type,
- TypeVar,
Union,
overload,
)
+from typing_extensions import TypeVar
+
from reflex import constants
from reflex.constants.base import REFLEX_VAR_OPENING_TAG
+from reflex.constants.colors import Color
from reflex.utils.exceptions import VarTypeError
from reflex.utils.types import GenericType, get_origin
from .base import (
CachedVarOperation,
CustomVarOperationReturn,
- LiteralNoneVar,
LiteralVar,
- ToOperation,
Var,
VarData,
_global_vars,
cached_property_no_lock,
figure_out_type,
+ get_python_literal,
get_unique_variable_name,
unionize,
var_operation,
@@ -50,13 +50,16 @@ from .number import (
LiteralNumberVar,
NumberVar,
raise_unsupported_operand_types,
+ ternary_operation,
)
if TYPE_CHECKING:
from .object import ObjectVar
+STRING_TYPE = TypeVar("STRING_TYPE", default=str)
-class StringVar(Var[str]):
+
+class StringVar(Var[STRING_TYPE], python_types=str):
"""Base class for immutable string vars."""
@overload
@@ -350,7 +353,7 @@ class StringVar(Var[str]):
@var_operation
-def string_lt_operation(lhs: StringVar | str, rhs: StringVar | str):
+def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
"""Check if a string is less than another string.
Args:
@@ -364,7 +367,7 @@ def string_lt_operation(lhs: StringVar | str, rhs: StringVar | str):
@var_operation
-def string_gt_operation(lhs: StringVar | str, rhs: StringVar | str):
+def string_gt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
"""Check if a string is greater than another string.
Args:
@@ -378,7 +381,7 @@ def string_gt_operation(lhs: StringVar | str, rhs: StringVar | str):
@var_operation
-def string_le_operation(lhs: StringVar | str, rhs: StringVar | str):
+def string_le_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
"""Check if a string is less than or equal to another string.
Args:
@@ -392,7 +395,7 @@ def string_le_operation(lhs: StringVar | str, rhs: StringVar | str):
@var_operation
-def string_ge_operation(lhs: StringVar | str, rhs: StringVar | str):
+def string_ge_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str):
"""Check if a string is greater than or equal to another string.
Args:
@@ -406,7 +409,7 @@ def string_ge_operation(lhs: StringVar | str, rhs: StringVar | str):
@var_operation
-def string_lower_operation(string: StringVar):
+def string_lower_operation(string: StringVar[Any]):
"""Convert a string to lowercase.
Args:
@@ -419,7 +422,7 @@ def string_lower_operation(string: StringVar):
@var_operation
-def string_upper_operation(string: StringVar):
+def string_upper_operation(string: StringVar[Any]):
"""Convert a string to uppercase.
Args:
@@ -432,7 +435,7 @@ def string_upper_operation(string: StringVar):
@var_operation
-def string_strip_operation(string: StringVar):
+def string_strip_operation(string: StringVar[Any]):
"""Strip a string.
Args:
@@ -446,7 +449,7 @@ def string_strip_operation(string: StringVar):
@var_operation
def string_contains_field_operation(
- haystack: StringVar, needle: StringVar | str, field: StringVar | str
+ haystack: StringVar[Any], needle: StringVar[Any] | str, field: StringVar[Any] | str
):
"""Check if a string contains another string.
@@ -465,7 +468,7 @@ def string_contains_field_operation(
@var_operation
-def string_contains_operation(haystack: StringVar, needle: StringVar | str):
+def string_contains_operation(haystack: StringVar[Any], needle: StringVar[Any] | str):
"""Check if a string contains another string.
Args:
@@ -481,7 +484,9 @@ def string_contains_operation(haystack: StringVar, needle: StringVar | str):
@var_operation
-def string_starts_with_operation(full_string: StringVar, prefix: StringVar | str):
+def string_starts_with_operation(
+ full_string: StringVar[Any], prefix: StringVar[Any] | str
+):
"""Check if a string starts with a prefix.
Args:
@@ -497,7 +502,7 @@ def string_starts_with_operation(full_string: StringVar, prefix: StringVar | str
@var_operation
-def string_item_operation(string: StringVar, index: NumberVar | int):
+def string_item_operation(string: StringVar[Any], index: NumberVar | int):
"""Get an item from a string.
Args:
@@ -511,7 +516,7 @@ def string_item_operation(string: StringVar, index: NumberVar | int):
@var_operation
-def array_join_operation(array: ArrayVar, sep: StringVar | str = ""):
+def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""):
"""Join the elements of an array.
Args:
@@ -524,6 +529,26 @@ def array_join_operation(array: ArrayVar, sep: StringVar | str = ""):
return var_operation_return(js_expression=f"{array}.join({sep})", var_type=str)
+@var_operation
+def string_replace_operation(
+ string: StringVar, search_value: StringVar | str, new_value: StringVar | str
+):
+ """Replace a string with a value.
+
+ Args:
+ string: The string.
+ search_value: The string to search.
+ new_value: The value to be replaced with.
+
+ Returns:
+ The string replace operation.
+ """
+ return var_operation_return(
+ js_expression=f"{string}.replace({search_value}, {new_value})",
+ var_type=str,
+ )
+
+
# Compile regex for finding reflex var tags.
_decode_var_pattern_re = (
rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}"
@@ -536,7 +561,7 @@ _decode_var_pattern = re.compile(_decode_var_pattern_re, flags=re.DOTALL)
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
-class LiteralStringVar(LiteralVar, StringVar):
+class LiteralStringVar(LiteralVar, StringVar[str]):
"""Base class for immutable literal string vars."""
_var_value: str = dataclasses.field(default="")
@@ -658,7 +683,7 @@ class LiteralStringVar(LiteralVar, StringVar):
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
-class ConcatVarOperation(CachedVarOperation, StringVar):
+class ConcatVarOperation(CachedVarOperation, StringVar[str]):
"""Representing a concatenation of literal string vars."""
_var_value: Tuple[Var, ...] = dataclasses.field(default_factory=tuple)
@@ -742,7 +767,7 @@ KEY_TYPE = TypeVar("KEY_TYPE")
VALUE_TYPE = TypeVar("VALUE_TYPE")
-class ArrayVar(Var[ARRAY_VAR_TYPE]):
+class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(list, tuple, set)):
"""Base class for immutable array vars."""
@overload
@@ -1275,7 +1300,7 @@ class LiteralArrayVar(CachedVarOperation, LiteralVar, ArrayVar[ARRAY_VAR_TYPE]):
@var_operation
-def string_split_operation(string: StringVar, sep: StringVar | str = ""):
+def string_split_operation(string: StringVar[Any], sep: StringVar | str = ""):
"""Split a string.
Args:
@@ -1572,32 +1597,6 @@ def array_contains_operation(haystack: ArrayVar, needle: Any | Var):
)
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToStringOperation(ToOperation, StringVar):
- """Base class for immutable string vars that are the result of a to string operation."""
-
- _original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
-
- _default_var_type: ClassVar[Type] = str
-
-
-@dataclasses.dataclass(
- eq=False,
- frozen=True,
- **{"slots": True} if sys.version_info >= (3, 10) else {},
-)
-class ToArrayOperation(ToOperation, ArrayVar):
- """Base class for immutable array vars that are the result of a to array operation."""
-
- _original: Var = dataclasses.field(default_factory=lambda: LiteralNoneVar.create())
-
- _default_var_type: ClassVar[Type] = List[Any]
-
-
@var_operation
def repeat_array_operation(
array: ArrayVar[ARRAY_VAR_TYPE], count: NumberVar | int
@@ -1657,3 +1656,134 @@ def array_concat_operation(
js_expression=f"[...{lhs}, ...{rhs}]",
var_type=Union[lhs._var_type, rhs._var_type],
)
+
+
+class ColorVar(StringVar[Color], python_types=Color):
+ """Base class for immutable color vars."""
+
+
+@dataclasses.dataclass(
+ eq=False,
+ frozen=True,
+ **{"slots": True} if sys.version_info >= (3, 10) else {},
+)
+class LiteralColorVar(CachedVarOperation, LiteralVar, ColorVar):
+ """Base class for immutable literal color vars."""
+
+ _var_value: Color = dataclasses.field(default_factory=lambda: Color(color="black"))
+
+ @classmethod
+ def create(
+ cls,
+ value: Color,
+ _var_type: Type[Color] | None = None,
+ _var_data: VarData | None = None,
+ ) -> ColorVar:
+ """Create a var from a string value.
+
+ Args:
+ value: The value to create the var from.
+ _var_type: The type of the var.
+ _var_data: Additional hooks and imports associated with the Var.
+
+ Returns:
+ The var.
+ """
+ return cls(
+ _js_expr="",
+ _var_type=_var_type or Color,
+ _var_data=_var_data,
+ _var_value=value,
+ )
+
+ def __hash__(self) -> int:
+ """Get the hash of the var.
+
+ Returns:
+ The hash of the var.
+ """
+ return hash(
+ (
+ self.__class__.__name__,
+ self._var_value.color,
+ self._var_value.alpha,
+ self._var_value.shade,
+ )
+ )
+
+ @cached_property_no_lock
+ def _cached_var_name(self) -> str:
+ """The name of the var.
+
+ Returns:
+ The name of the var.
+ """
+ alpha = self._var_value.alpha
+ alpha = (
+ ternary_operation(
+ alpha,
+ LiteralStringVar.create("a"),
+ LiteralStringVar.create(""),
+ )
+ if isinstance(alpha, Var)
+ else LiteralStringVar.create("a" if alpha else "")
+ )
+
+ shade = self._var_value.shade
+ shade = (
+ shade.to_string(use_json=False)
+ if isinstance(shade, Var)
+ else LiteralStringVar.create(str(shade))
+ )
+ return str(
+ ConcatVarOperation.create(
+ LiteralStringVar.create("var(--"),
+ self._var_value.color,
+ LiteralStringVar.create("-"),
+ alpha,
+ shade,
+ LiteralStringVar.create(")"),
+ )
+ )
+
+ @cached_property_no_lock
+ def _cached_get_all_var_data(self) -> VarData | None:
+ """Get all the var data.
+
+ Returns:
+ The var data.
+ """
+ return VarData.merge(
+ *[
+ LiteralVar.create(var)._get_all_var_data()
+ for var in (
+ self._var_value.color,
+ self._var_value.alpha,
+ self._var_value.shade,
+ )
+ ],
+ self._var_data,
+ )
+
+ def json(self) -> str:
+ """Get the JSON representation of the var.
+
+ Returns:
+ The JSON representation of the var.
+
+ Raises:
+ TypeError: If the color is not a valid color.
+ """
+ color, alpha, shade = map(
+ get_python_literal,
+ (self._var_value.color, self._var_value.alpha, self._var_value.shade),
+ )
+ if color is None or alpha is None or shade is None:
+ raise TypeError("Cannot serialize color that contains non-literal vars.")
+ if (
+ not isinstance(color, str)
+ or not isinstance(alpha, bool)
+ or not isinstance(shade, int)
+ ):
+ raise TypeError("Color is not a valid color.")
+ return f"var(--{color}-{'a' if alpha else ''}{shade})"
diff --git a/tests/integration/test_lifespan.py b/tests/integration/test_lifespan.py
index f8bcf2397..22c399c07 100644
--- a/tests/integration/test_lifespan.py
+++ b/tests/integration/test_lifespan.py
@@ -51,6 +51,7 @@ def LifespanApp():
def context_global(self) -> int:
return lifespan_context_global
+ @rx.event
def tick(self, date):
pass
diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py
index a6175d56a..74fbeb20f 100644
--- a/tests/units/components/core/test_colors.py
+++ b/tests/units/components/core/test_colors.py
@@ -14,6 +14,7 @@ class ColorState(rx.State):
color: str = "mint"
color_part: str = "tom"
shade: int = 4
+ alpha: bool = False
color_state_name = ColorState.get_full_name().replace(".", "__")
@@ -31,7 +32,14 @@ def create_color_var(color):
(create_color_var(rx.color("mint", 3, True)), '"var(--mint-a3)"', Color),
(
create_color_var(rx.color(ColorState.color, ColorState.shade)), # type: ignore
- f'("var(--"+{str(color_state_name)}.color+"-"+{str(color_state_name)}.shade+")")',
+ f'("var(--"+{str(color_state_name)}.color+"-"+(((__to_string) => __to_string.toString())({str(color_state_name)}.shade))+")")',
+ Color,
+ ),
+ (
+ create_color_var(
+ rx.color(ColorState.color, ColorState.shade, ColorState.alpha) # type: ignore
+ ),
+ f'("var(--"+{str(color_state_name)}.color+"-"+({str(color_state_name)}.alpha ? "a" : "")+(((__to_string) => __to_string.toString())({str(color_state_name)}.shade))+")")',
Color,
),
(
@@ -43,7 +51,7 @@ def create_color_var(color):
create_color_var(
rx.color(f"{ColorState.color_part}ato", f"{ColorState.shade}") # type: ignore
),
- f'("var(--"+{str(color_state_name)}.color_part+"ato-"+{str(color_state_name)}.shade+")")',
+ f'("var(--"+({str(color_state_name)}.color_part+"ato")+"-"+{str(color_state_name)}.shade+")")',
Color,
),
(
diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py
new file mode 100644
index 000000000..eb473ba06
--- /dev/null
+++ b/tests/units/components/datadisplay/test_shiki_code.py
@@ -0,0 +1,172 @@
+import pytest
+
+from reflex.components.datadisplay.shiki_code_block import (
+ ShikiBaseTransformers,
+ ShikiCodeBlock,
+ ShikiHighLevelCodeBlock,
+ ShikiJsTransformer,
+)
+from reflex.components.el.elements.forms import Button
+from reflex.components.lucide.icon import Icon
+from reflex.components.radix.themes.layout.box import Box
+from reflex.style import Style
+from reflex.vars import Var
+
+
+@pytest.mark.parametrize(
+ "library, fns, expected_output, raises_exception",
+ [
+ ("some_library", ["function_one"], ["function_one"], False),
+ ("some_library", [123], None, True),
+ ("some_library", [], [], False),
+ (
+ "some_library",
+ ["function_one", "function_two"],
+ ["function_one", "function_two"],
+ False,
+ ),
+ ("", ["function_one"], ["function_one"], False),
+ ("some_library", ["function_one", 789], None, True),
+ ("", [], [], False),
+ ],
+)
+def test_create_transformer(library, fns, expected_output, raises_exception):
+ if raises_exception:
+ # Ensure ValueError is raised for invalid cases
+ with pytest.raises(ValueError):
+ ShikiCodeBlock.create_transformer(library, fns)
+ else:
+ transformer = ShikiCodeBlock.create_transformer(library, fns)
+ assert isinstance(transformer, ShikiBaseTransformers)
+ assert transformer.library == library
+
+ # Verify that the functions are correctly wrapped in FunctionStringVar
+ function_names = [str(fn) for fn in transformer.fns]
+ assert function_names == expected_output
+
+
+@pytest.mark.parametrize(
+ "code_block, children, props, expected_first_child, expected_styles",
+ [
+ ("print('Hello')", ["print('Hello')"], {}, "print('Hello')", {}),
+ (
+ "print('Hello')",
+ ["print('Hello')", "More content"],
+ {},
+ "print('Hello')",
+ {},
+ ),
+ (
+ "print('Hello')",
+ ["print('Hello')"],
+ {
+ "transformers": [
+ ShikiBaseTransformers(
+ library="lib", fns=[], style=Style({"color": "red"})
+ )
+ ]
+ },
+ "print('Hello')",
+ {"color": "red"},
+ ),
+ (
+ "print('Hello')",
+ ["print('Hello')"],
+ {
+ "transformers": [
+ ShikiBaseTransformers(
+ library="lib", fns=[], style=Style({"color": "red"})
+ )
+ ],
+ "style": {"background": "blue"},
+ },
+ "print('Hello')",
+ {"color": "red", "background": "blue"},
+ ),
+ ],
+)
+def test_create_shiki_code_block(
+ code_block, children, props, expected_first_child, expected_styles
+):
+ component = ShikiCodeBlock.create(code_block, *children, **props)
+
+ # Test that the created component is a Box
+ assert isinstance(component, Box)
+
+ # Test that the first child is the code
+ code_block_component = component.children[0]
+ assert code_block_component.code._var_value == expected_first_child # type: ignore
+
+ applied_styles = component.style
+ for key, value in expected_styles.items():
+ assert Var.create(applied_styles[key])._var_value == value
+
+
+@pytest.mark.parametrize(
+ "children, props, expected_transformers, expected_button_type",
+ [
+ (["print('Hello')"], {"use_transformers": True}, [ShikiJsTransformer], None),
+ (["print('Hello')"], {"can_copy": True}, None, Button),
+ (
+ ["print('Hello')"],
+ {
+ "can_copy": True,
+ "copy_button": Button.create(Icon.create(tag="a_arrow_down")),
+ },
+ None,
+ Button,
+ ),
+ ],
+)
+def test_create_shiki_high_level_code_block(
+ children, props, expected_transformers, expected_button_type
+):
+ component = ShikiHighLevelCodeBlock.create(*children, **props)
+
+ # Test that the created component is a Box
+ assert isinstance(component, Box)
+
+ # Test that the first child is the code block component
+ code_block_component = component.children[0]
+ assert code_block_component.code._var_value == children[0] # type: ignore
+
+ # Check if the transformer is set correctly if expected
+ if expected_transformers:
+ exp_trans_names = [t.__name__ for t in expected_transformers]
+ for transformer in code_block_component.transformers._var_value: # type: ignore
+ assert type(transformer).__name__ in exp_trans_names
+
+ # Check if the second child is the copy button if can_copy is True
+ if props.get("can_copy", False):
+ if props.get("copy_button"):
+ assert isinstance(component.children[1], expected_button_type)
+ assert component.children[1] == props["copy_button"]
+ else:
+ assert isinstance(component.children[1], expected_button_type)
+ else:
+ assert len(component.children) == 1
+
+
+@pytest.mark.parametrize(
+ "children, props",
+ [
+ (["print('Hello')"], {"theme": "dark"}),
+ (["print('Hello')"], {"language": "javascript"}),
+ ],
+)
+def test_shiki_high_level_code_block_theme_language_mapping(children, props):
+ component = ShikiHighLevelCodeBlock.create(*children, **props)
+
+ # Test that the theme is mapped correctly
+ if "theme" in props:
+ assert component.children[
+ 0
+ ].theme._var_value == ShikiHighLevelCodeBlock._map_themes(props["theme"]) # type: ignore
+
+ # Test that the language is mapped correctly
+ if "language" in props:
+ assert component.children[
+ 0
+ ].language._var_value == ShikiHighLevelCodeBlock._map_languages( # type: ignore
+ props["language"]
+ )
diff --git a/tests/units/test_var.py b/tests/units/test_var.py
index c04e554a9..a8b4b759d 100644
--- a/tests/units/test_var.py
+++ b/tests/units/test_var.py
@@ -519,8 +519,8 @@ def test_var_indexing_types(var, type_):
type_ : The type on indexed object.
"""
- assert var[2]._var_type == type_[0]
- assert var[3]._var_type == type_[1]
+ assert var[0]._var_type == type_[0]
+ assert var[1]._var_type == type_[1]
def test_var_indexing_str():