Merge remote-tracking branch 'upstream/main' into state-compression
This commit is contained in:
commit
9d40c25a9c
14
.github/workflows/benchmarks.yml
vendored
14
.github/workflows/benchmarks.yml
vendored
@ -81,15 +81,13 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8"]
|
python-version: ["3.10.16", "3.11.11", "3.12.8"]
|
||||||
exclude:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.16"
|
python-version: "3.10.16"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.9.21"
|
python-version: "3.11.11"
|
||||||
# keep only one python version for MacOS
|
# keep only one python version for MacOS
|
||||||
- os: macos-latest
|
|
||||||
python-version: "3.9.21"
|
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
python-version: "3.10.16"
|
python-version: "3.10.16"
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
@ -98,7 +96,7 @@ jobs:
|
|||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.11"
|
python-version: "3.10.11"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.9.13"
|
python-version: "3.11.9"
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
@ -161,7 +159,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up python
|
||||||
|
id: setup-python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install Poetry
|
- name: Install Poetry
|
||||||
uses: snok/install-poetry@v1
|
uses: snok/install-poetry@v1
|
||||||
with:
|
with:
|
||||||
|
@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/setup_build_env
|
- uses: ./.github/actions/setup_build_env
|
||||||
with:
|
with:
|
||||||
python-version: "3.9.21"
|
python-version: '3.10'
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ jobs:
|
|||||||
path: reflex-web
|
path: reflex-web
|
||||||
- name: Install Requirements for reflex-web
|
- name: Install Requirements for reflex-web
|
||||||
working-directory: ./reflex-web
|
working-directory: ./reflex-web
|
||||||
run: poetry run uv pip install -r requirements.txt
|
run: poetry run uv pip install $(grep -ivE "reflex " requirements.txt)
|
||||||
- name: Install additional dependencies for DB access
|
- name: Install additional dependencies for DB access
|
||||||
run: poetry run uv pip install psycopg
|
run: poetry run uv pip install psycopg
|
||||||
- name: Init Website for reflex-web
|
- name: Init Website for reflex-web
|
||||||
@ -73,7 +73,7 @@ jobs:
|
|||||||
echo "$outdated"
|
echo "$outdated"
|
||||||
|
|
||||||
# Ignore 3rd party dependencies that are not updated.
|
# Ignore 3rd party dependencies that are not updated.
|
||||||
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true)
|
filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images|ag-grid' || true)
|
||||||
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
|
no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true)
|
||||||
|
|
||||||
|
|
||||||
|
11
.github/workflows/integration_app_harness.yml
vendored
11
.github/workflows/integration_app_harness.yml
vendored
@ -47,17 +47,10 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
run-poetry-install: true
|
run-poetry-install: true
|
||||||
create-venv-at-path: .venv
|
create-venv-at-path: .venv
|
||||||
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split
|
- run: poetry run uv pip install pyvirtualdisplay pillow pytest-split pytest-retry
|
||||||
- name: Run app harness tests
|
- name: Run app harness tests
|
||||||
env:
|
env:
|
||||||
SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }}
|
|
||||||
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }}
|
||||||
run: |
|
run: |
|
||||||
poetry run playwright install chromium
|
poetry run playwright install chromium
|
||||||
poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}}
|
poetry run pytest tests/integration --retries 3 --maxfail=5 --splits 2 --group ${{matrix.split_index}}
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
name: Upload failed test screenshots
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: failed_test_screenshots
|
|
||||||
path: /tmp/screenshots
|
|
||||||
|
33
.github/workflows/integration_tests.yml
vendored
33
.github/workflows/integration_tests.yml
vendored
@ -33,7 +33,7 @@ env:
|
|||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
example-counter:
|
example-counter-and-nba-proxy:
|
||||||
env:
|
env:
|
||||||
OUTPUT_FILE: import_benchmark.json
|
OUTPUT_FILE: import_benchmark.json
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
@ -43,22 +43,17 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
# Show OS combos first in GUI
|
# Show OS combos first in GUI
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
python-version: ['3.10.16', '3.11.11', '3.12.8', '3.13.1']
|
||||||
# Windows is a bit behind on Python version availability in Github
|
|
||||||
exclude:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.11.11"
|
python-version: "3.11.11"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.16"
|
python-version: '3.10.16'
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.9.21"
|
|
||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.11.9"
|
python-version: "3.11.9"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.11"
|
python-version: '3.10.11'
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.9.13"
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
@ -119,6 +114,26 @@ jobs:
|
|||||||
--benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}"
|
--benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}"
|
||||||
--branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
|
--branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}"
|
||||||
--app-name "counter"
|
--app-name "counter"
|
||||||
|
- name: Install requirements for nba proxy example
|
||||||
|
working-directory: ./reflex-examples/nba-proxy
|
||||||
|
run: |
|
||||||
|
poetry run uv pip install -r requirements.txt
|
||||||
|
- name: Install additional dependencies for DB access
|
||||||
|
run: poetry run uv pip install psycopg
|
||||||
|
- name: Check export --backend-only before init for nba-proxy example
|
||||||
|
working-directory: ./reflex-examples/nba-proxy
|
||||||
|
run: |
|
||||||
|
poetry run reflex export --backend-only
|
||||||
|
- name: Init Website for nba-proxy example
|
||||||
|
working-directory: ./reflex-examples/nba-proxy
|
||||||
|
run: |
|
||||||
|
poetry run reflex init --loglevel debug
|
||||||
|
- name: Run Website and Check for errors
|
||||||
|
run: |
|
||||||
|
# Check that npm is home
|
||||||
|
npm -v
|
||||||
|
poetry run bash scripts/integration.sh ./reflex-examples/nba-proxy dev
|
||||||
|
|
||||||
|
|
||||||
reflex-web:
|
reflex-web:
|
||||||
strategy:
|
strategy:
|
||||||
|
10
.github/workflows/unit_tests.yml
vendored
10
.github/workflows/unit_tests.yml
vendored
@ -28,22 +28,18 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
python-version: ["3.10.16", "3.11.11", "3.12.8", "3.13.1"]
|
||||||
# Windows is a bit behind on Python version availability in Github
|
# Windows is a bit behind on Python version availability in Github
|
||||||
exclude:
|
exclude:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.11.11"
|
python-version: "3.11.11"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.16"
|
python-version: "3.10.16"
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.9.21"
|
|
||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.11.9"
|
python-version: "3.11.9"
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
python-version: "3.10.11"
|
python-version: "3.10.11"
|
||||||
- os: windows-latest
|
|
||||||
python-version: "3.9.13"
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
# Service containers to run with `runner-job`
|
# Service containers to run with `runner-job`
|
||||||
@ -92,8 +88,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Note: py39, py310, py311 versions chosen due to available arm64 darwin builds.
|
# Note: py310, py311 versions chosen due to available arm64 darwin builds.
|
||||||
python-version: ["3.9.13", "3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
python-version: ["3.10.11", "3.11.9", "3.12.8", "3.13.1"]
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@ assets/external/*
|
|||||||
dist/*
|
dist/*
|
||||||
examples/
|
examples/
|
||||||
.web
|
.web
|
||||||
|
.states
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.coverage
|
.coverage
|
||||||
|
@ -28,7 +28,7 @@ repos:
|
|||||||
entry: python3 scripts/make_pyi.py
|
entry: python3 scripts/make_pyi.py
|
||||||
|
|
||||||
- repo: https://github.com/RobertCraigie/pyright-python
|
- repo: https://github.com/RobertCraigie/pyright-python
|
||||||
rev: v1.1.313
|
rev: v1.1.392
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyright
|
- id: pyright
|
||||||
args: [reflex, tests]
|
args: [reflex, tests]
|
||||||
|
@ -8,7 +8,7 @@ Here is a quick guide on how to run Reflex repo locally so you can start contrib
|
|||||||
|
|
||||||
**Prerequisites:**
|
**Prerequisites:**
|
||||||
|
|
||||||
- Python >= 3.9
|
- Python >= 3.10
|
||||||
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
|
- Poetry version >= 1.4.0 and add it to your path (see [Poetry Docs](https://python-poetry.org/docs/#installation) for more info).
|
||||||
|
|
||||||
**1. Fork this repository:**
|
**1. Fork this repository:**
|
||||||
@ -87,7 +87,7 @@ poetry run ruff format .
|
|||||||
```
|
```
|
||||||
|
|
||||||
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
|
Consider installing git pre-commit hooks so Ruff, Pyright, Darglint and `make_pyi` will run automatically before each commit.
|
||||||
Note that pre-commit will only be installed when you use a Python version >= 3.9.
|
Note that pre-commit will only be installed when you use a Python version >= 3.10.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
pre-commit install
|
pre-commit install
|
||||||
|
@ -34,7 +34,7 @@ See our [architecture page](https://reflex.dev/blog/2024-03-21-reflex-architectu
|
|||||||
|
|
||||||
## ⚙️ Installation
|
## ⚙️ Installation
|
||||||
|
|
||||||
Open a terminal and run (Requires Python 3.9+):
|
Open a terminal and run (Requires Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -21,7 +21,7 @@ def get_package_size(venv_path: Path, os_name):
|
|||||||
ValueError: when venv does not exist or python version is None.
|
ValueError: when venv does not exist or python version is None.
|
||||||
"""
|
"""
|
||||||
python_version = get_python_version(venv_path, os_name)
|
python_version = get_python_version(venv_path, os_name)
|
||||||
print("Python version:", python_version) # noqa: T201
|
print("Python version:", python_version)
|
||||||
if python_version is None:
|
if python_version is None:
|
||||||
raise ValueError("Error: Failed to determine Python version.")
|
raise ValueError("Error: Failed to determine Python version.")
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ def render_component(num: int):
|
|||||||
rx.box(
|
rx.box(
|
||||||
rx.accordion.root(
|
rx.accordion.root(
|
||||||
rx.accordion.item(
|
rx.accordion.item(
|
||||||
header="Full Ingredients", # type: ignore
|
header="Full Ingredients",
|
||||||
content="Yes. It's built with accessibility in mind.", # type: ignore
|
content="Yes. It's built with accessibility in mind.",
|
||||||
font_size="3em",
|
font_size="3em",
|
||||||
),
|
),
|
||||||
rx.accordion.item(
|
rx.accordion.item(
|
||||||
header="Applications", # type: ignore
|
header="Applications",
|
||||||
content="Yes. It's unstyled by default, giving you freedom over the look and feel.", # type: ignore
|
content="Yes. It's unstyled by default, giving you freedom over the look and feel.",
|
||||||
),
|
),
|
||||||
collapsible=True,
|
collapsible=True,
|
||||||
variant="ghost",
|
variant="ghost",
|
||||||
@ -122,7 +122,7 @@ def AppWithTenComponentsOnePage():
|
|||||||
def index() -> rx.Component:
|
def index() -> rx.Component:
|
||||||
return rx.center(rx.vstack(*render_component(1)))
|
return rx.center(rx.vstack(*render_component(1)))
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
app.add_page(index)
|
app.add_page(index)
|
||||||
|
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ def AppWithHundredComponentOnePage():
|
|||||||
def index() -> rx.Component:
|
def index() -> rx.Component:
|
||||||
return rx.center(rx.vstack(*render_component(100)))
|
return rx.center(rx.vstack(*render_component(100)))
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
app.add_page(index)
|
app.add_page(index)
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ def AppWithThousandComponentsOnePage():
|
|||||||
def index() -> rx.Component:
|
def index() -> rx.Component:
|
||||||
return rx.center(rx.vstack(*render_component(1000)))
|
return rx.center(rx.vstack(*render_component(1000)))
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
app.add_page(index)
|
app.add_page(index)
|
||||||
|
|
||||||
|
|
||||||
@ -166,9 +166,9 @@ def app_with_10_components(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithTenComponentsOnePage,
|
AppWithTenComponentsOnePage,
|
||||||
render_component=render_component, # type: ignore
|
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -189,9 +189,9 @@ def app_with_100_components(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithHundredComponentOnePage,
|
AppWithHundredComponentOnePage,
|
||||||
render_component=render_component, # type: ignore
|
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -212,9 +212,9 @@ def app_with_1000_components(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithThousandComponentsOnePage,
|
AppWithThousandComponentsOnePage,
|
||||||
render_component=render_component, # type: ignore
|
render_component=render_component, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
||||||
|
@ -28,7 +28,7 @@ def render_multiple_pages(app, num: int):
|
|||||||
"""
|
"""
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from rxconfig import config # type: ignore
|
from rxconfig import config # pyright: ignore [reportMissingImports]
|
||||||
|
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
@ -74,13 +74,13 @@ def render_multiple_pages(app, num: int):
|
|||||||
rx.select(
|
rx.select(
|
||||||
["C", "PF", "SF", "PG", "SG"],
|
["C", "PF", "SF", "PG", "SG"],
|
||||||
placeholder="Select a position. (All)",
|
placeholder="Select a position. (All)",
|
||||||
on_change=State.set_position, # type: ignore
|
on_change=State.set_position, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
size="3",
|
size="3",
|
||||||
),
|
),
|
||||||
rx.select(
|
rx.select(
|
||||||
college,
|
college,
|
||||||
placeholder="Select a college. (All)",
|
placeholder="Select a college. (All)",
|
||||||
on_change=State.set_college, # type: ignore
|
on_change=State.set_college, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
size="3",
|
size="3",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -95,7 +95,7 @@ def render_multiple_pages(app, num: int):
|
|||||||
default_value=[18, 50],
|
default_value=[18, 50],
|
||||||
min=18,
|
min=18,
|
||||||
max=50,
|
max=50,
|
||||||
on_value_commit=State.set_age, # type: ignore
|
on_value_commit=State.set_age, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
),
|
),
|
||||||
align_items="left",
|
align_items="left",
|
||||||
width="100%",
|
width="100%",
|
||||||
@ -110,7 +110,7 @@ def render_multiple_pages(app, num: int):
|
|||||||
default_value=[0, 25000000],
|
default_value=[0, 25000000],
|
||||||
min=0,
|
min=0,
|
||||||
max=25000000,
|
max=25000000,
|
||||||
on_value_commit=State.set_salary, # type: ignore
|
on_value_commit=State.set_salary, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
),
|
),
|
||||||
align_items="left",
|
align_items="left",
|
||||||
width="100%",
|
width="100%",
|
||||||
@ -130,7 +130,7 @@ def render_multiple_pages(app, num: int):
|
|||||||
|
|
||||||
def AppWithOnePage():
|
def AppWithOnePage():
|
||||||
"""A reflex app with one page."""
|
"""A reflex app with one page."""
|
||||||
from rxconfig import config # type: ignore
|
from rxconfig import config # pyright: ignore [reportMissingImports]
|
||||||
|
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ def AppWithOnePage():
|
|||||||
height="100vh",
|
height="100vh",
|
||||||
)
|
)
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
app.add_page(index)
|
app.add_page(index)
|
||||||
|
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ def AppWithTenPages():
|
|||||||
"""A reflex app with 10 pages."""
|
"""A reflex app with 10 pages."""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
render_multiple_pages(app, 10)
|
render_multiple_pages(app, 10)
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ def AppWithHundredPages():
|
|||||||
"""A reflex app with 100 pages."""
|
"""A reflex app with 100 pages."""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
render_multiple_pages(app, 100)
|
render_multiple_pages(app, 100)
|
||||||
|
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ def AppWithThousandPages():
|
|||||||
"""A reflex app with Thousand pages."""
|
"""A reflex app with Thousand pages."""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
render_multiple_pages(app, 1000)
|
render_multiple_pages(app, 1000)
|
||||||
|
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ def AppWithTenThousandPages():
|
|||||||
"""A reflex app with ten thousand pages."""
|
"""A reflex app with ten thousand pages."""
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
|
|
||||||
app = rx.App(state=rx.State)
|
app = rx.App(_state=rx.State)
|
||||||
render_multiple_pages(app, 10000)
|
render_multiple_pages(app, 10000)
|
||||||
|
|
||||||
|
|
||||||
@ -232,7 +232,7 @@ def app_with_ten_pages(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithTenPages,
|
AppWithTenPages,
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -255,9 +255,9 @@ def app_with_hundred_pages(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithHundredPages,
|
AppWithHundredPages,
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -278,9 +278,9 @@ def app_with_thousand_pages(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithThousandPages,
|
AppWithThousandPages,
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
@ -301,9 +301,9 @@ def app_with_ten_thousand_pages(
|
|||||||
root=root,
|
root=root,
|
||||||
app_source=functools.partial(
|
app_source=functools.partial(
|
||||||
AppWithTenThousandPages,
|
AppWithTenThousandPages,
|
||||||
render_comp=render_multiple_pages, # type: ignore
|
render_comp=render_multiple_pages, # pyright: ignore [reportCallIssue]
|
||||||
),
|
),
|
||||||
) # type: ignore
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
@pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
|
||||||
|
@ -34,7 +34,7 @@ Auf unserer [Architektur-Seite](https://reflex.dev/blog/2024-03-21-reflex-archit
|
|||||||
|
|
||||||
## ⚙️ Installation
|
## ⚙️ Installation
|
||||||
|
|
||||||
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.9+):
|
Öffne ein Terminal und führe den folgenden Befehl aus (benötigt Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -35,7 +35,7 @@ Consulta nuestra [página de arquitectura](https://reflex.dev/blog/2024-03-21-re
|
|||||||
|
|
||||||
## ⚙️ Instalación
|
## ⚙️ Instalación
|
||||||
|
|
||||||
Abra un terminal y ejecute (Requiere Python 3.9+):
|
Abra un terminal y ejecute (Requiere Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -35,7 +35,7 @@ Reflex के अंदर के कामकाज को जानने क
|
|||||||
|
|
||||||
## ⚙️ इंस्टॉलेशन (Installation)
|
## ⚙️ इंस्टॉलेशन (Installation)
|
||||||
|
|
||||||
एक टर्मिनल खोलें और चलाएं (Python 3.9+ की आवश्यकता है):
|
एक टर्मिनल खोलें और चलाएं (Python 3.10+ की आवश्यकता है):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
## ⚙️ Installazione
|
## ⚙️ Installazione
|
||||||
|
|
||||||
Apri un terminale ed esegui (Richiede Python 3.9+):
|
Apri un terminale ed esegui (Richiede Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -37,7 +37,7 @@ Reflex がどのように動作しているかを知るには、[アーキテク
|
|||||||
|
|
||||||
## ⚙️ インストール
|
## ⚙️ インストール
|
||||||
|
|
||||||
ターミナルを開いて以下のコマンドを実行してください。(Python 3.9 以上が必要です。):
|
ターミナルを開いて以下のコマンドを実行してください。(Python 3.10 以上が必要です。):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
---
|
---
|
||||||
## ⚙️ 설치
|
## ⚙️ 설치
|
||||||
|
|
||||||
터미널을 열고 실행하세요. (Python 3.9+ 필요):
|
터미널을 열고 실행하세요. (Python 3.10+ 필요):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
## ⚙️ Installation - نصب و راه اندازی
|
## ⚙️ Installation - نصب و راه اندازی
|
||||||
|
|
||||||
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.9+):
|
یک ترمینال را باز کنید و اجرا کنید (نیازمند Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
---
|
---
|
||||||
## ⚙️ Instalação
|
## ⚙️ Instalação
|
||||||
|
|
||||||
Abra um terminal e execute (Requer Python 3.9+):
|
Abra um terminal e execute (Requer Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
## ⚙️ Kurulum
|
## ⚙️ Kurulum
|
||||||
|
|
||||||
Bir terminal açın ve çalıştırın (Python 3.9+ gerekir):
|
Bir terminal açın ve çalıştırın (Python 3.10+ gerekir):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -34,7 +34,7 @@ Các tính năng chính:
|
|||||||
|
|
||||||
## ⚙️ Cài đặt
|
## ⚙️ Cài đặt
|
||||||
|
|
||||||
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.9+):
|
Mở cửa sổ lệnh và chạy (Yêu cầu Python phiên bản 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -34,7 +34,7 @@ Reflex 是一个使用纯Python构建全栈web应用的库。
|
|||||||
|
|
||||||
## ⚙️ 安装
|
## ⚙️ 安装
|
||||||
|
|
||||||
打开一个终端并且运行(要求Python3.9+):
|
打开一个终端并且运行(要求Python3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
@ -36,7 +36,7 @@ Reflex 是一個可以用純 Python 構建全端網頁應用程式的函式庫
|
|||||||
|
|
||||||
## ⚙️ 安裝
|
## ⚙️ 安裝
|
||||||
|
|
||||||
開啟一個終端機並且執行 (需要 Python 3.9+):
|
開啟一個終端機並且執行 (需要 Python 3.10+):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install reflex
|
pip install reflex
|
||||||
|
1687
poetry.lock
generated
1687
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@ keywords = ["web", "framework"]
|
|||||||
classifiers = ["Development Status :: 4 - Beta"]
|
classifiers = ["Development Status :: 4 - Beta"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = ">=3.10, <4.0"
|
||||||
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
fastapi = ">=0.96.0,!=0.111.0,!=0.111.1"
|
||||||
gunicorn = ">=20.1.0,<24.0"
|
gunicorn = ">=20.1.0,<24.0"
|
||||||
jinja2 = ">=3.1.2,<4.0"
|
jinja2 = ">=3.1.2,<4.0"
|
||||||
@ -50,13 +50,12 @@ httpx = ">=0.25.1,<1.0"
|
|||||||
twine = ">=4.0.0,<7.0"
|
twine = ">=4.0.0,<7.0"
|
||||||
tomlkit = ">=0.12.4,<1.0"
|
tomlkit = ">=0.12.4,<1.0"
|
||||||
lazy_loader = ">=0.4"
|
lazy_loader = ">=0.4"
|
||||||
reflex-chakra = ">=0.6.0"
|
|
||||||
typing_extensions = ">=4.6.0"
|
typing_extensions = ">=4.6.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = ">=7.1.2,<9.0"
|
pytest = ">=7.1.2,<9.0"
|
||||||
pytest-mock = ">=3.10.0,<4.0"
|
pytest-mock = ">=3.10.0,<4.0"
|
||||||
pyright = ">=1.1.229,<1.1.335"
|
pyright = ">=1.1.392, <1.2"
|
||||||
darglint = ">=1.8.1,<2.0"
|
darglint = ">=1.8.1,<2.0"
|
||||||
dill = ">=0.3.8"
|
dill = ">=0.3.8"
|
||||||
toml = ">=0.10.2,<1.0"
|
toml = ">=0.10.2,<1.0"
|
||||||
@ -82,20 +81,24 @@ requires = ["poetry-core>=1.5.1"]
|
|||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.pyright]
|
[tool.pyright]
|
||||||
|
reportIncompatibleMethodOverride = false
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py39"
|
target-version = "py310"
|
||||||
output-format = "concise"
|
output-format = "concise"
|
||||||
lint.isort.split-on-trailing-comma = false
|
lint.isort.split-on-trailing-comma = false
|
||||||
lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "W"]
|
lint.select = ["ANN001","B", "C4", "D", "E", "ERA", "F", "FURB", "I", "N", "PERF", "PGH", "PTH", "RUF", "SIM", "T", "TRY", "W"]
|
||||||
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012"]
|
lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"]
|
||||||
lint.pydocstyle.convention = "google"
|
lint.pydocstyle.convention = "google"
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"__init__.py" = ["F401"]
|
"__init__.py" = ["F401"]
|
||||||
"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T"]
|
"tests/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"]
|
||||||
|
"benchmarks/*.py" = ["ANN001", "D100", "D103", "D104", "B018", "PERF", "T", "N"]
|
||||||
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
|
"reflex/.templates/*.py" = ["D100", "D103", "D104"]
|
||||||
"*.pyi" = ["D301", "D415", "D417", "D418", "E742"]
|
"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"]
|
||||||
|
"pyi_generator.py" = ["N802"]
|
||||||
|
"reflex/constants/*.py" = ["N"]
|
||||||
"*/blank.py" = ["I001"]
|
"*/blank.py" = ["I001"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
@ -103,5 +106,5 @@ asyncio_default_fixture_loop_scope = "function"
|
|||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
|
|
||||||
[tool.codespell]
|
[tool.codespell]
|
||||||
skip = "docs/*,*.html,examples/*, *.pyi"
|
skip = "docs/*,*.html,examples/*, *.pyi, poetry.lock"
|
||||||
ignore-words-list = "te, TreeE"
|
ignore-words-list = "te, TreeE"
|
||||||
|
@ -8,7 +8,7 @@ version = "0.0.1"
|
|||||||
description = "Reflex custom component {{ module_name }}"
|
description = "Reflex custom component {{ module_name }}"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "Apache-2.0" }
|
license = { text = "Apache-2.0" }
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.10"
|
||||||
authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
|
authors = [{ name = "", email = "YOUREMAIL@domain.com" }]
|
||||||
keywords = ["reflex","reflex-custom-components"]
|
keywords = ["reflex","reflex-custom-components"]
|
||||||
|
|
||||||
|
@ -86,11 +86,11 @@
|
|||||||
{% for condition in case[:-1] %}
|
{% for condition in case[:-1] %}
|
||||||
case JSON.stringify({{ condition._js_expr }}):
|
case JSON.stringify({{ condition._js_expr }}):
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
return {{ case[-1] }};
|
return {{ render(case[-1]) }};
|
||||||
break;
|
break;
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
default:
|
default:
|
||||||
return {{ component.default }};
|
return {{ render(component.default) }};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -16,10 +16,7 @@ export default function RadixThemesColorModeProvider({ children }) {
|
|||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
const lastCompiledTimeInLocalStorage =
|
const lastCompiledTimeInLocalStorage =
|
||||||
localStorage.getItem("last_compiled_time");
|
localStorage.getItem("last_compiled_time");
|
||||||
if (
|
if (lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp) {
|
||||||
lastCompiledTimeInLocalStorage &&
|
|
||||||
lastCompiledTimeInLocalStorage !== lastCompiledTimeStamp
|
|
||||||
) {
|
|
||||||
// on app startup, make sure the application color mode is persisted correctly.
|
// on app startup, make sure the application color mode is persisted correctly.
|
||||||
setTheme(defaultColorMode);
|
setTheme(defaultColorMode);
|
||||||
localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
|
localStorage.setItem("last_compiled_time", lastCompiledTimeStamp);
|
||||||
|
@ -3,6 +3,7 @@ import axios from "axios";
|
|||||||
import io from "socket.io-client";
|
import io from "socket.io-client";
|
||||||
import JSON5 from "json5";
|
import JSON5 from "json5";
|
||||||
import env from "$/env.json";
|
import env from "$/env.json";
|
||||||
|
import reflexEnvironment from "$/reflex.json";
|
||||||
import Cookies from "universal-cookie";
|
import Cookies from "universal-cookie";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import Router, { useRouter } from "next/router";
|
import Router, { useRouter } from "next/router";
|
||||||
@ -407,6 +408,7 @@ export const connect = async (
|
|||||||
socket.current = io(endpoint.href, {
|
socket.current = io(endpoint.href, {
|
||||||
path: endpoint["pathname"],
|
path: endpoint["pathname"],
|
||||||
transports: transports,
|
transports: transports,
|
||||||
|
protocols: [reflexEnvironment.version],
|
||||||
autoUnref: false,
|
autoUnref: false,
|
||||||
});
|
});
|
||||||
// Ensure undefined fields in events are sent as null instead of removed
|
// Ensure undefined fields in events are sent as null instead of removed
|
||||||
|
@ -84,6 +84,9 @@ In the example above, you will be able to do `rx.list`
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from reflex.utils import (
|
from reflex.utils import (
|
||||||
compat, # for side-effects
|
compat, # for side-effects
|
||||||
lazy_loader,
|
lazy_loader,
|
||||||
@ -303,7 +306,6 @@ _MAPPING: dict = {
|
|||||||
"event": [
|
"event": [
|
||||||
"EventChain",
|
"EventChain",
|
||||||
"EventHandler",
|
"EventHandler",
|
||||||
"background",
|
|
||||||
"call_script",
|
"call_script",
|
||||||
"call_function",
|
"call_function",
|
||||||
"run_script",
|
"run_script",
|
||||||
@ -366,20 +368,5 @@ getattr, __dir__, __all__ = lazy_loader.attach(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name: ModuleType | Any):
|
||||||
if name == "chakra":
|
|
||||||
from reflex.utils import console
|
|
||||||
|
|
||||||
console.deprecate(
|
|
||||||
"rx.chakra",
|
|
||||||
reason="and moved to a separate package. "
|
|
||||||
"To continue using Chakra UI components, install the `reflex-chakra` package via `pip install "
|
|
||||||
"reflex-chakra`.",
|
|
||||||
deprecation_version="0.6.0",
|
|
||||||
removal_version="0.7.0",
|
|
||||||
dedupe=True,
|
|
||||||
)
|
|
||||||
import reflex_chakra as rc
|
|
||||||
|
|
||||||
return rc
|
|
||||||
return getattr(name)
|
return getattr(name)
|
||||||
|
@ -131,7 +131,7 @@ from .components.radix.themes.layout.container import container as container
|
|||||||
from .components.radix.themes.layout.flex import flex as flex
|
from .components.radix.themes.layout.flex import flex as flex
|
||||||
from .components.radix.themes.layout.grid import grid as grid
|
from .components.radix.themes.layout.grid import grid as grid
|
||||||
from .components.radix.themes.layout.list import list_item as list_item
|
from .components.radix.themes.layout.list import list_item as list_item
|
||||||
from .components.radix.themes.layout.list import list_ns as list # noqa
|
from .components.radix.themes.layout.list import list_ns as list # noqa: F401
|
||||||
from .components.radix.themes.layout.list import ordered_list as ordered_list
|
from .components.radix.themes.layout.list import ordered_list as ordered_list
|
||||||
from .components.radix.themes.layout.list import unordered_list as unordered_list
|
from .components.radix.themes.layout.list import unordered_list as unordered_list
|
||||||
from .components.radix.themes.layout.section import section as section
|
from .components.radix.themes.layout.section import section as section
|
||||||
@ -156,7 +156,6 @@ from .constants import Env as Env
|
|||||||
from .constants.colors import Color as Color
|
from .constants.colors import Color as Color
|
||||||
from .event import EventChain as EventChain
|
from .event import EventChain as EventChain
|
||||||
from .event import EventHandler as EventHandler
|
from .event import EventHandler as EventHandler
|
||||||
from .event import background as background
|
|
||||||
from .event import call_function as call_function
|
from .event import call_function as call_function
|
||||||
from .event import call_script as call_script
|
from .event import call_script as call_script
|
||||||
from .event import clear_local_storage as clear_local_storage
|
from .event import clear_local_storage as clear_local_storage
|
||||||
|
274
reflex/app.py
274
reflex/app.py
@ -27,6 +27,7 @@ from typing import (
|
|||||||
Dict,
|
Dict,
|
||||||
Generic,
|
Generic,
|
||||||
List,
|
List,
|
||||||
|
MutableMapping,
|
||||||
Optional,
|
Optional,
|
||||||
Set,
|
Set,
|
||||||
Type,
|
Type,
|
||||||
@ -145,7 +146,7 @@ def default_backend_exception_handler(exception: Exception) -> EventSpec:
|
|||||||
position="top-center",
|
position="top-center",
|
||||||
id="backend_error",
|
id="backend_error",
|
||||||
style={"width": "500px"},
|
style={"width": "500px"},
|
||||||
) # type: ignore
|
) # pyright: ignore [reportReturnType]
|
||||||
else:
|
else:
|
||||||
error_message.insert(0, "An error occurred.")
|
error_message.insert(0, "An error occurred.")
|
||||||
return window_alert("\n".join(error_message))
|
return window_alert("\n".join(error_message))
|
||||||
@ -251,36 +252,36 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# Attributes to add to the html root tag of every page.
|
# Attributes to add to the html root tag of every page.
|
||||||
html_custom_attrs: Optional[Dict[str, str]] = None
|
html_custom_attrs: Optional[Dict[str, str]] = None
|
||||||
|
|
||||||
# A map from a route to an unevaluated page. PRIVATE.
|
# A map from a route to an unevaluated page.
|
||||||
unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
_unevaluated_pages: Dict[str, UnevaluatedPage] = dataclasses.field(
|
||||||
default_factory=dict
|
default_factory=dict
|
||||||
)
|
)
|
||||||
|
|
||||||
# A map from a page route to the component to render. Users should use `add_page`. PRIVATE.
|
# A map from a page route to the component to render. Users should use `add_page`.
|
||||||
pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
_pages: Dict[str, Component] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
# The backend API object. PRIVATE.
|
# The backend API object.
|
||||||
api: FastAPI = None # type: ignore
|
_api: FastAPI | None = None
|
||||||
|
|
||||||
# The state class to use for the app. PRIVATE.
|
# The state class to use for the app.
|
||||||
state: Optional[Type[BaseState]] = None
|
_state: Optional[Type[BaseState]] = None
|
||||||
|
|
||||||
# Class to manage many client states.
|
# Class to manage many client states.
|
||||||
_state_manager: Optional[StateManager] = None
|
_state_manager: Optional[StateManager] = None
|
||||||
|
|
||||||
# Mapping from a route to event handlers to trigger when the page loads. PRIVATE.
|
# Mapping from a route to event handlers to trigger when the page loads.
|
||||||
load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
_load_events: Dict[str, List[IndividualEventType[[], Any]]] = dataclasses.field(
|
||||||
default_factory=dict
|
default_factory=dict
|
||||||
)
|
)
|
||||||
|
|
||||||
# Admin dashboard to view and manage the database. PRIVATE.
|
# Admin dashboard to view and manage the database.
|
||||||
admin_dash: Optional[AdminDash] = None
|
admin_dash: Optional[AdminDash] = None
|
||||||
|
|
||||||
# The async server name space. PRIVATE.
|
# The async server name space.
|
||||||
event_namespace: Optional[EventNamespace] = None
|
_event_namespace: Optional[EventNamespace] = None
|
||||||
|
|
||||||
# Background tasks that are currently running. PRIVATE.
|
# Background tasks that are currently running.
|
||||||
background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
_background_tasks: Set[asyncio.Task] = dataclasses.field(default_factory=set)
|
||||||
|
|
||||||
# Frontend Error Handler Function
|
# Frontend Error Handler Function
|
||||||
frontend_exception_handler: Callable[[Exception], None] = (
|
frontend_exception_handler: Callable[[Exception], None] = (
|
||||||
@ -292,6 +293,24 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
[Exception], Union[EventSpec, List[EventSpec], None]
|
[Exception], Union[EventSpec, List[EventSpec], None]
|
||||||
] = default_backend_exception_handler
|
] = default_backend_exception_handler
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api(self) -> FastAPI | None:
|
||||||
|
"""Get the backend api.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The backend api.
|
||||||
|
"""
|
||||||
|
return self._api
|
||||||
|
|
||||||
|
@property
|
||||||
|
def event_namespace(self) -> EventNamespace | None:
|
||||||
|
"""Get the event namespace.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The event namespace.
|
||||||
|
"""
|
||||||
|
return self._event_namespace
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
"""Initialize the app.
|
"""Initialize the app.
|
||||||
|
|
||||||
@ -311,7 +330,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
set_breakpoints(self.style.pop("breakpoints"))
|
set_breakpoints(self.style.pop("breakpoints"))
|
||||||
|
|
||||||
# Set up the API.
|
# Set up the API.
|
||||||
self.api = FastAPI(lifespan=self._run_lifespan_tasks)
|
self._api = FastAPI(lifespan=self._run_lifespan_tasks)
|
||||||
self._add_cors()
|
self._add_cors()
|
||||||
self._add_default_endpoints()
|
self._add_default_endpoints()
|
||||||
|
|
||||||
@ -334,8 +353,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
def _enable_state(self) -> None:
|
def _enable_state(self) -> None:
|
||||||
"""Enable state for the app."""
|
"""Enable state for the app."""
|
||||||
if not self.state:
|
if not self._state:
|
||||||
self.state = State
|
self._state = State
|
||||||
self._setup_state()
|
self._setup_state()
|
||||||
|
|
||||||
def _setup_state(self) -> None:
|
def _setup_state(self) -> None:
|
||||||
@ -344,13 +363,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Raises:
|
Raises:
|
||||||
RuntimeError: If the socket server is invalid.
|
RuntimeError: If the socket server is invalid.
|
||||||
"""
|
"""
|
||||||
if not self.state:
|
if not self._state:
|
||||||
return
|
return
|
||||||
|
|
||||||
config = get_config()
|
config = get_config()
|
||||||
|
|
||||||
# Set up the state manager.
|
# Set up the state manager.
|
||||||
self._state_manager = StateManager.create(state=self.state)
|
self._state_manager = StateManager.create(state=self._state)
|
||||||
|
|
||||||
# Set up the Socket.IO AsyncServer.
|
# Set up the Socket.IO AsyncServer.
|
||||||
if not self.sio:
|
if not self.sio:
|
||||||
@ -381,12 +400,42 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
namespace = config.get_event_namespace()
|
namespace = config.get_event_namespace()
|
||||||
|
|
||||||
# Create the event namespace and attach the main app. Not related to any paths.
|
# Create the event namespace and attach the main app. Not related to any paths.
|
||||||
self.event_namespace = EventNamespace(namespace, self)
|
self._event_namespace = EventNamespace(namespace, self)
|
||||||
|
|
||||||
# Register the event namespace with the socket.
|
# Register the event namespace with the socket.
|
||||||
self.sio.register_namespace(self.event_namespace)
|
self.sio.register_namespace(self.event_namespace)
|
||||||
# Mount the socket app with the API.
|
# Mount the socket app with the API.
|
||||||
self.api.mount(str(constants.Endpoint.EVENT), socket_app)
|
if self.api:
|
||||||
|
|
||||||
|
class HeaderMiddleware:
|
||||||
|
def __init__(self, app: ASGIApp):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
async def __call__(
|
||||||
|
self, scope: MutableMapping[str, Any], receive: Any, send: Callable
|
||||||
|
):
|
||||||
|
original_send = send
|
||||||
|
|
||||||
|
async def modified_send(message: dict):
|
||||||
|
if message["type"] == "websocket.accept":
|
||||||
|
if scope.get("subprotocols"):
|
||||||
|
# The following *does* say "subprotocol" instead of "subprotocols", intentionally.
|
||||||
|
message["subprotocol"] = scope["subprotocols"][0]
|
||||||
|
|
||||||
|
headers = dict(message.get("headers", []))
|
||||||
|
header_key = b"sec-websocket-protocol"
|
||||||
|
if subprotocol := headers.get(header_key):
|
||||||
|
message["headers"] = [
|
||||||
|
*message.get("headers", []),
|
||||||
|
(header_key, subprotocol),
|
||||||
|
]
|
||||||
|
|
||||||
|
return await original_send(message)
|
||||||
|
|
||||||
|
return await self.app(scope, receive, modified_send)
|
||||||
|
|
||||||
|
socket_app_with_headers = HeaderMiddleware(socket_app)
|
||||||
|
self.api.mount(str(constants.Endpoint.EVENT), socket_app_with_headers)
|
||||||
|
|
||||||
# Check the exception handlers
|
# Check the exception handlers
|
||||||
self._validate_exception_handlers()
|
self._validate_exception_handlers()
|
||||||
@ -397,24 +446,35 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Returns:
|
Returns:
|
||||||
The string representation of the app.
|
The string representation of the app.
|
||||||
"""
|
"""
|
||||||
return f"<App state={self.state.__name__ if self.state else None}>"
|
return f"<App state={self._state.__name__ if self._state else None}>"
|
||||||
|
|
||||||
def __call__(self) -> FastAPI:
|
def __call__(self) -> FastAPI:
|
||||||
"""Run the backend api instance.
|
"""Run the backend api instance.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the app has not been initialized.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The backend api.
|
The backend api.
|
||||||
"""
|
"""
|
||||||
|
if not self.api:
|
||||||
|
raise ValueError("The app has not been initialized.")
|
||||||
return self.api
|
return self.api
|
||||||
|
|
||||||
def _add_default_endpoints(self):
|
def _add_default_endpoints(self):
|
||||||
"""Add default api endpoints (ping)."""
|
"""Add default api endpoints (ping)."""
|
||||||
# To test the server.
|
# To test the server.
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
|
|
||||||
self.api.get(str(constants.Endpoint.PING))(ping)
|
self.api.get(str(constants.Endpoint.PING))(ping)
|
||||||
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
self.api.get(str(constants.Endpoint.HEALTH))(health)
|
||||||
|
|
||||||
def _add_optional_endpoints(self):
|
def _add_optional_endpoints(self):
|
||||||
"""Add optional api endpoints (_upload)."""
|
"""Add optional api endpoints (_upload)."""
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
|
|
||||||
if Upload.is_used:
|
if Upload.is_used:
|
||||||
# To upload files.
|
# To upload files.
|
||||||
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
self.api.post(str(constants.Endpoint.UPLOAD))(upload(self))
|
||||||
@ -432,6 +492,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
def _add_cors(self):
|
def _add_cors(self):
|
||||||
"""Add CORS middleware to the app."""
|
"""Add CORS middleware to the app."""
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
self.api.add_middleware(
|
self.api.add_middleware(
|
||||||
cors.CORSMiddleware,
|
cors.CORSMiddleware,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
@ -463,14 +525,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The generated component.
|
The generated component.
|
||||||
|
|
||||||
Raises:
|
|
||||||
exceptions.MatchTypeError: If the return types of match cases in rx.match are different.
|
|
||||||
"""
|
"""
|
||||||
try:
|
return component if isinstance(component, Component) else component()
|
||||||
return component if isinstance(component, Component) else component()
|
|
||||||
except exceptions.MatchTypeError:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def add_page(
|
def add_page(
|
||||||
self,
|
self,
|
||||||
@ -527,13 +583,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# Check if the route given is valid
|
# Check if the route given is valid
|
||||||
verify_route_validity(route)
|
verify_route_validity(route)
|
||||||
|
|
||||||
if route in self.unevaluated_pages and environment.RELOAD_CONFIG.is_set():
|
if route in self._unevaluated_pages and environment.RELOAD_CONFIG.is_set():
|
||||||
# when the app is reloaded(typically for app harness tests), we should maintain
|
# when the app is reloaded(typically for app harness tests), we should maintain
|
||||||
# the latest render function of a route.This applies typically to decorated pages
|
# the latest render function of a route.This applies typically to decorated pages
|
||||||
# since they are only added when app._compile is called.
|
# since they are only added when app._compile is called.
|
||||||
self.unevaluated_pages.pop(route)
|
self._unevaluated_pages.pop(route)
|
||||||
|
|
||||||
if route in self.unevaluated_pages:
|
if route in self._unevaluated_pages:
|
||||||
route_name = (
|
route_name = (
|
||||||
f"`{route}` or `/`"
|
f"`{route}` or `/`"
|
||||||
if route == constants.PageNames.INDEX_ROUTE
|
if route == constants.PageNames.INDEX_ROUTE
|
||||||
@ -546,15 +602,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
# Setup dynamic args for the route.
|
# Setup dynamic args for the route.
|
||||||
# this state assignment is only required for tests using the deprecated state kwarg for App
|
# this state assignment is only required for tests using the deprecated state kwarg for App
|
||||||
state = self.state if self.state else State
|
state = self._state if self._state else State
|
||||||
state.setup_dynamic_args(get_route_args(route))
|
state.setup_dynamic_args(get_route_args(route))
|
||||||
|
|
||||||
if on_load:
|
if on_load:
|
||||||
self.load_events[route] = (
|
self._load_events[route] = (
|
||||||
on_load if isinstance(on_load, list) else [on_load]
|
on_load if isinstance(on_load, list) else [on_load]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.unevaluated_pages[route] = UnevaluatedPage(
|
self._unevaluated_pages[route] = UnevaluatedPage(
|
||||||
component=component,
|
component=component,
|
||||||
route=route,
|
route=route,
|
||||||
title=title,
|
title=title,
|
||||||
@ -564,14 +620,15 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
meta=meta,
|
meta=meta,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _compile_page(self, route: str):
|
def _compile_page(self, route: str, save_page: bool = True):
|
||||||
"""Compile a page.
|
"""Compile a page.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
route: The route of the page to compile.
|
route: The route of the page to compile.
|
||||||
|
save_page: If True, the compiled page is saved to self._pages.
|
||||||
"""
|
"""
|
||||||
component, enable_state = compiler.compile_unevaluated_page(
|
component, enable_state = compiler.compile_unevaluated_page(
|
||||||
route, self.unevaluated_pages[route], self.state, self.style, self.theme
|
route, self._unevaluated_pages[route], self._state, self.style, self.theme
|
||||||
)
|
)
|
||||||
|
|
||||||
if enable_state:
|
if enable_state:
|
||||||
@ -579,7 +636,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
# Add the page.
|
# Add the page.
|
||||||
self._check_routes_conflict(route)
|
self._check_routes_conflict(route)
|
||||||
self.pages[route] = component
|
if save_page:
|
||||||
|
self._pages[route] = component
|
||||||
|
|
||||||
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
|
def get_load_events(self, route: str) -> list[IndividualEventType[[], Any]]:
|
||||||
"""Get the load events for a route.
|
"""Get the load events for a route.
|
||||||
@ -593,7 +651,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
route = route.lstrip("/")
|
route = route.lstrip("/")
|
||||||
if route == "":
|
if route == "":
|
||||||
route = constants.PageNames.INDEX_ROUTE
|
route = constants.PageNames.INDEX_ROUTE
|
||||||
return self.load_events.get(route, [])
|
return self._load_events.get(route, [])
|
||||||
|
|
||||||
def _check_routes_conflict(self, new_route: str):
|
def _check_routes_conflict(self, new_route: str):
|
||||||
"""Verify if there is any conflict between the new route and any existing route.
|
"""Verify if there is any conflict between the new route and any existing route.
|
||||||
@ -617,10 +675,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
constants.RouteRegex.SINGLE_CATCHALL_SEGMENT,
|
||||||
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
||||||
)
|
)
|
||||||
for route in self.pages:
|
for route in self._pages:
|
||||||
replaced_route = replace_brackets_with_keywords(route)
|
replaced_route = replace_brackets_with_keywords(route)
|
||||||
for rw, r, nr in zip(
|
for rw, r, nr in zip(
|
||||||
replaced_route.split("/"), route.split("/"), new_route.split("/")
|
replaced_route.split("/"),
|
||||||
|
route.split("/"),
|
||||||
|
new_route.split("/"),
|
||||||
|
strict=False,
|
||||||
):
|
):
|
||||||
if rw in segments and r != nr:
|
if rw in segments and r != nr:
|
||||||
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
# If the slugs in the segments of both routes are not the same, then the route is invalid
|
||||||
@ -651,8 +712,8 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Args:
|
Args:
|
||||||
component: The component to display at the page.
|
component: The component to display at the page.
|
||||||
title: The title of the page.
|
title: The title of the page.
|
||||||
description: The description of the page.
|
|
||||||
image: The image to display on the page.
|
image: The image to display on the page.
|
||||||
|
description: The description of the page.
|
||||||
on_load: The event handler(s) that will be called each time the page load.
|
on_load: The event handler(s) that will be called each time the page load.
|
||||||
meta: The metadata of the page.
|
meta: The metadata of the page.
|
||||||
"""
|
"""
|
||||||
@ -675,6 +736,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
def _setup_admin_dash(self):
|
def _setup_admin_dash(self):
|
||||||
"""Setup the admin dash."""
|
"""Setup the admin dash."""
|
||||||
# Get the admin dash.
|
# Get the admin dash.
|
||||||
|
if not self.api:
|
||||||
|
return
|
||||||
|
|
||||||
admin_dash = self.admin_dash
|
admin_dash = self.admin_dash
|
||||||
|
|
||||||
if admin_dash and admin_dash.models:
|
if admin_dash and admin_dash.models:
|
||||||
@ -716,7 +780,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
frontend_packages = get_config().frontend_packages
|
frontend_packages = get_config().frontend_packages
|
||||||
_frontend_packages = []
|
_frontend_packages = []
|
||||||
for package in frontend_packages:
|
for package in frontend_packages:
|
||||||
if package in (get_config().tailwind or {}).get("plugins", []): # type: ignore
|
if package in (get_config().tailwind or {}).get("plugins", []):
|
||||||
console.warn(
|
console.warn(
|
||||||
f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`"
|
f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`"
|
||||||
)
|
)
|
||||||
@ -779,10 +843,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
def _setup_overlay_component(self):
|
def _setup_overlay_component(self):
|
||||||
"""If a State is not used and no overlay_component is specified, do not render the connection modal."""
|
"""If a State is not used and no overlay_component is specified, do not render the connection modal."""
|
||||||
if self.state is None and self.overlay_component is default_overlay_component:
|
if self._state is None and self.overlay_component is default_overlay_component:
|
||||||
self.overlay_component = None
|
self.overlay_component = None
|
||||||
for k, component in self.pages.items():
|
for k, component in self._pages.items():
|
||||||
self.pages[k] = self._add_overlay_to_component(component)
|
self._pages[k] = self._add_overlay_to_component(component)
|
||||||
|
|
||||||
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
def _add_error_boundary_to_component(self, component: Component) -> Component:
|
||||||
if self.error_boundary is None:
|
if self.error_boundary is None:
|
||||||
@ -794,14 +858,14 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
def _setup_error_boundary(self):
|
def _setup_error_boundary(self):
|
||||||
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
|
"""If a State is not used and no error_boundary is specified, do not render the error boundary."""
|
||||||
if self.state is None and self.error_boundary is default_error_boundary:
|
if self._state is None and self.error_boundary is default_error_boundary:
|
||||||
self.error_boundary = None
|
self.error_boundary = None
|
||||||
|
|
||||||
for k, component in self.pages.items():
|
for k, component in self._pages.items():
|
||||||
# Skip the 404 page
|
# Skip the 404 page
|
||||||
if k == constants.Page404.SLUG:
|
if k == constants.Page404.SLUG:
|
||||||
continue
|
continue
|
||||||
self.pages[k] = self._add_error_boundary_to_component(component)
|
self._pages[k] = self._add_error_boundary_to_component(component)
|
||||||
|
|
||||||
def _apply_decorated_pages(self):
|
def _apply_decorated_pages(self):
|
||||||
"""Add @rx.page decorated pages to the app.
|
"""Add @rx.page decorated pages to the app.
|
||||||
@ -827,11 +891,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
Raises:
|
Raises:
|
||||||
VarDependencyError: When a computed var has an invalid dependency.
|
VarDependencyError: When a computed var has an invalid dependency.
|
||||||
"""
|
"""
|
||||||
if not self.state:
|
if not self._state:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not state:
|
if not state:
|
||||||
state = self.state
|
state = self._state
|
||||||
|
|
||||||
for var in state.computed_vars.values():
|
for var in state.computed_vars.values():
|
||||||
if not var._cache:
|
if not var._cache:
|
||||||
@ -857,13 +921,13 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
"""
|
"""
|
||||||
from reflex.utils.exceptions import ReflexRuntimeError
|
from reflex.utils.exceptions import ReflexRuntimeError
|
||||||
|
|
||||||
self.pages = {}
|
self._pages = {}
|
||||||
|
|
||||||
def get_compilation_time() -> str:
|
def get_compilation_time() -> str:
|
||||||
return str(datetime.now().time()).split(".")[0]
|
return str(datetime.now().time()).split(".")[0]
|
||||||
|
|
||||||
# Render a default 404 page if the user didn't supply one
|
# Render a default 404 page if the user didn't supply one
|
||||||
if constants.Page404.SLUG not in self.unevaluated_pages:
|
if constants.Page404.SLUG not in self._unevaluated_pages:
|
||||||
self.add_page(route=constants.Page404.SLUG)
|
self.add_page(route=constants.Page404.SLUG)
|
||||||
|
|
||||||
# Fix up the style.
|
# Fix up the style.
|
||||||
@ -879,14 +943,16 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
# If a theme component was provided, wrap the app with it
|
# If a theme component was provided, wrap the app with it
|
||||||
app_wrappers[(20, "Theme")] = self.theme
|
app_wrappers[(20, "Theme")] = self.theme
|
||||||
|
|
||||||
for route in self.unevaluated_pages:
|
should_compile = self._should_compile()
|
||||||
|
|
||||||
|
for route in self._unevaluated_pages:
|
||||||
console.debug(f"Evaluating page: {route}")
|
console.debug(f"Evaluating page: {route}")
|
||||||
self._compile_page(route)
|
self._compile_page(route, save_page=should_compile)
|
||||||
|
|
||||||
# Add the optional endpoints (_upload)
|
# Add the optional endpoints (_upload)
|
||||||
self._add_optional_endpoints()
|
self._add_optional_endpoints()
|
||||||
|
|
||||||
if not self._should_compile():
|
if not should_compile:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._validate_var_dependencies()
|
self._validate_var_dependencies()
|
||||||
@ -906,7 +972,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
progress.start()
|
progress.start()
|
||||||
task = progress.add_task(
|
task = progress.add_task(
|
||||||
f"[{get_compilation_time()}] Compiling:",
|
f"[{get_compilation_time()}] Compiling:",
|
||||||
total=len(self.pages)
|
total=len(self._pages)
|
||||||
+ fixed_pages_within_executor
|
+ fixed_pages_within_executor
|
||||||
+ adhoc_steps_without_executor,
|
+ adhoc_steps_without_executor,
|
||||||
)
|
)
|
||||||
@ -925,7 +991,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
# This has to happen before compiling stateful components as that
|
# This has to happen before compiling stateful components as that
|
||||||
# prevents recursive functions from reaching all components.
|
# prevents recursive functions from reaching all components.
|
||||||
for component in self.pages.values():
|
for component in self._pages.values():
|
||||||
# Add component._get_all_imports() to all_imports.
|
# Add component._get_all_imports() to all_imports.
|
||||||
all_imports.update(component._get_all_imports())
|
all_imports.update(component._get_all_imports())
|
||||||
|
|
||||||
@ -940,12 +1006,12 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
stateful_components_path,
|
stateful_components_path,
|
||||||
stateful_components_code,
|
stateful_components_code,
|
||||||
page_components,
|
page_components,
|
||||||
) = compiler.compile_stateful_components(self.pages.values())
|
) = compiler.compile_stateful_components(self._pages.values())
|
||||||
|
|
||||||
progress.advance(task)
|
progress.advance(task)
|
||||||
|
|
||||||
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
# Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
|
||||||
if code_uses_state_contexts(stateful_components_code) and self.state is None:
|
if code_uses_state_contexts(stateful_components_code) and self._state is None:
|
||||||
raise ReflexRuntimeError(
|
raise ReflexRuntimeError(
|
||||||
"To access rx.State in frontend components, at least one "
|
"To access rx.State in frontend components, at least one "
|
||||||
"subclass of rx.State must be defined in the app."
|
"subclass of rx.State must be defined in the app."
|
||||||
@ -959,7 +1025,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
compiler.compile_document_root(
|
compiler.compile_document_root(
|
||||||
self.head_components,
|
self.head_components,
|
||||||
html_lang=self.html_lang,
|
html_lang=self.html_lang,
|
||||||
html_custom_attrs=self.html_custom_attrs, # type: ignore
|
html_custom_attrs=self.html_custom_attrs, # pyright: ignore [reportArgumentType]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -982,20 +1048,20 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
max_workers=environment.REFLEX_COMPILE_THREADS.get() or None
|
||||||
)
|
)
|
||||||
|
|
||||||
for route, component in zip(self.pages, page_components):
|
for route, component in zip(self._pages, page_components, strict=True):
|
||||||
ExecutorSafeFunctions.COMPONENTS[route] = component
|
ExecutorSafeFunctions.COMPONENTS[route] = component
|
||||||
|
|
||||||
ExecutorSafeFunctions.STATE = self.state
|
ExecutorSafeFunctions.STATE = self._state
|
||||||
|
|
||||||
with executor:
|
with executor:
|
||||||
result_futures = []
|
result_futures = []
|
||||||
|
|
||||||
def _submit_work(fn, *args, **kwargs):
|
def _submit_work(fn: Callable, *args, **kwargs):
|
||||||
f = executor.submit(fn, *args, **kwargs)
|
f = executor.submit(fn, *args, **kwargs)
|
||||||
result_futures.append(f)
|
result_futures.append(f)
|
||||||
|
|
||||||
# Compile the pre-compiled pages.
|
# Compile the pre-compiled pages.
|
||||||
for route in self.pages:
|
for route in self._pages:
|
||||||
_submit_work(
|
_submit_work(
|
||||||
ExecutorSafeFunctions.compile_page,
|
ExecutorSafeFunctions.compile_page,
|
||||||
route,
|
route,
|
||||||
@ -1030,7 +1096,7 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
|
|
||||||
# Compile the contexts.
|
# Compile the contexts.
|
||||||
compile_results.append(
|
compile_results.append(
|
||||||
compiler.compile_contexts(self.state, self.theme),
|
compiler.compile_contexts(self._state, self.theme),
|
||||||
)
|
)
|
||||||
if self.theme is not None:
|
if self.theme is not None:
|
||||||
# Fix #2992 by removing the top-level appearance prop
|
# Fix #2992 by removing the top-level appearance prop
|
||||||
@ -1152,9 +1218,9 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
task = asyncio.create_task(_coro())
|
task = asyncio.create_task(_coro())
|
||||||
self.background_tasks.add(task)
|
self._background_tasks.add(task)
|
||||||
# Clean up task from background_tasks set when complete.
|
# Clean up task from background_tasks set when complete.
|
||||||
task.add_done_callback(self.background_tasks.discard)
|
task.add_done_callback(self._background_tasks.discard)
|
||||||
return task
|
return task
|
||||||
|
|
||||||
def _validate_exception_handlers(self):
|
def _validate_exception_handlers(self):
|
||||||
@ -1164,11 +1230,11 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
ValueError: If the custom exception handlers are invalid.
|
ValueError: If the custom exception handlers are invalid.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
FRONTEND_ARG_SPEC = {
|
frontend_arg_spec = {
|
||||||
"exception": Exception,
|
"exception": Exception,
|
||||||
}
|
}
|
||||||
|
|
||||||
BACKEND_ARG_SPEC = {
|
backend_arg_spec = {
|
||||||
"exception": Exception,
|
"exception": Exception,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1176,9 +1242,10 @@ class App(MiddlewareMixin, LifespanMixin):
|
|||||||
["frontend", "backend"],
|
["frontend", "backend"],
|
||||||
[self.frontend_exception_handler, self.backend_exception_handler],
|
[self.frontend_exception_handler, self.backend_exception_handler],
|
||||||
[
|
[
|
||||||
FRONTEND_ARG_SPEC,
|
frontend_arg_spec,
|
||||||
BACKEND_ARG_SPEC,
|
backend_arg_spec,
|
||||||
],
|
],
|
||||||
|
strict=True,
|
||||||
):
|
):
|
||||||
if hasattr(handler_fn, "__name__"):
|
if hasattr(handler_fn, "__name__"):
|
||||||
_fn_name = handler_fn.__name__
|
_fn_name = handler_fn.__name__
|
||||||
@ -1320,15 +1387,14 @@ async def process(
|
|||||||
if app._process_background(state, event) is not None:
|
if app._process_background(state, event) is not None:
|
||||||
# `final=True` allows the frontend send more events immediately.
|
# `final=True` allows the frontend send more events immediately.
|
||||||
yield StateUpdate(final=True)
|
yield StateUpdate(final=True)
|
||||||
return
|
else:
|
||||||
|
# Process the event synchronously.
|
||||||
|
async for update in state._process(event):
|
||||||
|
# Postprocess the event.
|
||||||
|
update = await app._postprocess(state, event, update)
|
||||||
|
|
||||||
# Process the event synchronously.
|
# Yield the update.
|
||||||
async for update in state._process(event):
|
yield update
|
||||||
# Postprocess the event.
|
|
||||||
update = await app._postprocess(state, event, update)
|
|
||||||
|
|
||||||
# Yield the update.
|
|
||||||
yield update
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
telemetry.send_error(ex, context="backend")
|
telemetry.send_error(ex, context="backend")
|
||||||
|
|
||||||
@ -1523,16 +1589,20 @@ class EventNamespace(AsyncNamespace):
|
|||||||
self.sid_to_token = {}
|
self.sid_to_token = {}
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
def on_connect(self, sid, environ):
|
def on_connect(self, sid: str, environ: dict):
|
||||||
"""Event for when the websocket is connected.
|
"""Event for when the websocket is connected.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sid: The Socket.IO session id.
|
sid: The Socket.IO session id.
|
||||||
environ: The request information, including HTTP headers.
|
environ: The request information, including HTTP headers.
|
||||||
"""
|
"""
|
||||||
pass
|
subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL")
|
||||||
|
if subprotocol and subprotocol != constants.Reflex.VERSION:
|
||||||
|
console.warn(
|
||||||
|
f"Frontend version {subprotocol} for session {sid} does not match the backend version {constants.Reflex.VERSION}."
|
||||||
|
)
|
||||||
|
|
||||||
def on_disconnect(self, sid):
|
def on_disconnect(self, sid: str):
|
||||||
"""Event for when the websocket disconnects.
|
"""Event for when the websocket disconnects.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1554,7 +1624,7 @@ class EventNamespace(AsyncNamespace):
|
|||||||
self.emit(str(constants.SocketEvent.EVENT), update, to=sid)
|
self.emit(str(constants.SocketEvent.EVENT), update, to=sid)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def on_event(self, sid, data):
|
async def on_event(self, sid: str, data: Any):
|
||||||
"""Event for receiving front-end websocket events.
|
"""Event for receiving front-end websocket events.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
@ -1563,10 +1633,36 @@ class EventNamespace(AsyncNamespace):
|
|||||||
Args:
|
Args:
|
||||||
sid: The Socket.IO session id.
|
sid: The Socket.IO session id.
|
||||||
data: The event data.
|
data: The event data.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
EventDeserializationError: If the event data is not a dictionary.
|
||||||
"""
|
"""
|
||||||
fields = data
|
fields = data
|
||||||
# Get the event.
|
|
||||||
event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS})
|
if isinstance(fields, str):
|
||||||
|
console.warn(
|
||||||
|
"Received event data as a string. This generally should not happen and may indicate a bug."
|
||||||
|
f" Event data: {fields}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
fields = json.loads(fields)
|
||||||
|
except json.JSONDecodeError as ex:
|
||||||
|
raise exceptions.EventDeserializationError(
|
||||||
|
f"Failed to deserialize event data: {fields}."
|
||||||
|
) from ex
|
||||||
|
|
||||||
|
if not isinstance(fields, dict):
|
||||||
|
raise exceptions.EventDeserializationError(
|
||||||
|
f"Event data must be a dictionary, but received {fields} of type {type(fields)}."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get the event.
|
||||||
|
event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS})
|
||||||
|
except (TypeError, ValueError) as ex:
|
||||||
|
raise exceptions.EventDeserializationError(
|
||||||
|
f"Failed to deserialize event data: {fields}."
|
||||||
|
) from ex
|
||||||
|
|
||||||
self.token_to_sid[event.token] = sid
|
self.token_to_sid[event.token] = sid
|
||||||
self.sid_to_token[sid] = event.token
|
self.sid_to_token[sid] = event.token
|
||||||
@ -1595,7 +1691,7 @@ class EventNamespace(AsyncNamespace):
|
|||||||
# Emit the update from processing the event.
|
# Emit the update from processing the event.
|
||||||
await self.emit_update(update=update, sid=sid)
|
await self.emit_update(update=update, sid=sid)
|
||||||
|
|
||||||
async def on_ping(self, sid):
|
async def on_ping(self, sid: str):
|
||||||
"""Event for testing the API endpoint.
|
"""Event for testing the API endpoint.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -12,7 +12,7 @@ from typing import Callable, Coroutine, Set, Union
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from reflex.utils import console
|
from reflex.utils import console
|
||||||
from reflex.utils.exceptions import InvalidLifespanTaskType
|
from reflex.utils.exceptions import InvalidLifespanTaskTypeError
|
||||||
|
|
||||||
from .mixin import AppMixin
|
from .mixin import AppMixin
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class LifespanMixin(AppMixin):
|
|||||||
try:
|
try:
|
||||||
async with contextlib.AsyncExitStack() as stack:
|
async with contextlib.AsyncExitStack() as stack:
|
||||||
for task in self.lifespan_tasks:
|
for task in self.lifespan_tasks:
|
||||||
run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # type: ignore
|
run_msg = f"Started lifespan task: {task.__name__} as {{type}}" # pyright: ignore [reportAttributeAccessIssue]
|
||||||
if isinstance(task, asyncio.Task):
|
if isinstance(task, asyncio.Task):
|
||||||
running_tasks.append(task)
|
running_tasks.append(task)
|
||||||
else:
|
else:
|
||||||
@ -61,19 +61,19 @@ class LifespanMixin(AppMixin):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
task: The task to register.
|
task: The task to register.
|
||||||
task_kwargs: The kwargs of the task.
|
**task_kwargs: The kwargs of the task.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidLifespanTaskType: If the task is a generator function.
|
InvalidLifespanTaskTypeError: If the task is a generator function.
|
||||||
"""
|
"""
|
||||||
if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task):
|
if inspect.isgeneratorfunction(task) or inspect.isasyncgenfunction(task):
|
||||||
raise InvalidLifespanTaskType(
|
raise InvalidLifespanTaskTypeError(
|
||||||
f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
|
f"Task {task.__name__} of type generator must be decorated with contextlib.asynccontextmanager."
|
||||||
)
|
)
|
||||||
|
|
||||||
if task_kwargs:
|
if task_kwargs:
|
||||||
original_task = task
|
original_task = task
|
||||||
task = functools.partial(task, **task_kwargs) # type: ignore
|
task = functools.partial(task, **task_kwargs) # pyright: ignore [reportArgumentType]
|
||||||
functools.update_wrapper(task, original_task) # type: ignore
|
functools.update_wrapper(task, original_task) # pyright: ignore [reportArgumentType]
|
||||||
self.lifespan_tasks.add(task) # type: ignore
|
self.lifespan_tasks.add(task)
|
||||||
console.debug(f"Registered lifespan task: {task.__name__}") # type: ignore
|
console.debug(f"Registered lifespan task: {task.__name__}") # pyright: ignore [reportAttributeAccessIssue]
|
||||||
|
@ -53,11 +53,11 @@ class MiddlewareMixin(AppMixin):
|
|||||||
"""
|
"""
|
||||||
for middleware in self.middleware:
|
for middleware in self.middleware:
|
||||||
if asyncio.iscoroutinefunction(middleware.preprocess):
|
if asyncio.iscoroutinefunction(middleware.preprocess):
|
||||||
out = await middleware.preprocess(app=self, state=state, event=event) # type: ignore
|
out = await middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType]
|
||||||
else:
|
else:
|
||||||
out = middleware.preprocess(app=self, state=state, event=event) # type: ignore
|
out = middleware.preprocess(app=self, state=state, event=event) # pyright: ignore [reportArgumentType]
|
||||||
if out is not None:
|
if out is not None:
|
||||||
return out # type: ignore
|
return out # pyright: ignore [reportReturnType]
|
||||||
|
|
||||||
async def _postprocess(
|
async def _postprocess(
|
||||||
self, state: BaseState, event: Event, update: StateUpdate
|
self, state: BaseState, event: Event, update: StateUpdate
|
||||||
@ -78,18 +78,18 @@ class MiddlewareMixin(AppMixin):
|
|||||||
for middleware in self.middleware:
|
for middleware in self.middleware:
|
||||||
if asyncio.iscoroutinefunction(middleware.postprocess):
|
if asyncio.iscoroutinefunction(middleware.postprocess):
|
||||||
out = await middleware.postprocess(
|
out = await middleware.postprocess(
|
||||||
app=self, # type: ignore
|
app=self, # pyright: ignore [reportArgumentType]
|
||||||
state=state,
|
state=state,
|
||||||
event=event,
|
event=event,
|
||||||
update=update,
|
update=update,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
out = middleware.postprocess(
|
out = middleware.postprocess(
|
||||||
app=self, # type: ignore
|
app=self, # pyright: ignore [reportArgumentType]
|
||||||
state=state,
|
state=state,
|
||||||
event=event,
|
event=event,
|
||||||
update=update,
|
update=update,
|
||||||
)
|
)
|
||||||
if out is not None:
|
if out is not None:
|
||||||
return out # type: ignore
|
return out # pyright: ignore [reportReturnType]
|
||||||
return update
|
return update
|
||||||
|
@ -5,16 +5,13 @@ Only the app attribute is explicitly exposed.
|
|||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.utils import telemetry
|
|
||||||
from reflex.utils.exec import is_prod_mode
|
from reflex.utils.exec import is_prod_mode
|
||||||
from reflex.utils.prerequisites import get_app
|
from reflex.utils.prerequisites import get_and_validate_app
|
||||||
|
|
||||||
if constants.CompileVars.APP != "app":
|
if constants.CompileVars.APP != "app":
|
||||||
raise AssertionError("unexpected variable name for 'app'")
|
raise AssertionError("unexpected variable name for 'app'")
|
||||||
|
|
||||||
telemetry.send("compile")
|
app, app_module = get_and_validate_app(reload=False)
|
||||||
app_module = get_app(reload=False)
|
|
||||||
app = getattr(app_module, constants.CompileVars.APP)
|
|
||||||
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
# For py3.9 compatibility when redis is used, we MUST add any decorator pages
|
||||||
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
# before compiling the app in a thread to avoid event loop error (REF-2172).
|
||||||
app._apply_decorated_pages()
|
app._apply_decorated_pages()
|
||||||
@ -30,8 +27,7 @@ if is_prod_mode():
|
|||||||
# ensure only "app" is exposed.
|
# ensure only "app" is exposed.
|
||||||
del app_module
|
del app_module
|
||||||
del compile_future
|
del compile_future
|
||||||
del get_app
|
del get_and_validate_app
|
||||||
del is_prod_mode
|
del is_prod_mode
|
||||||
del telemetry
|
|
||||||
del constants
|
del constants
|
||||||
del ThreadPoolExecutor
|
del ThreadPoolExecutor
|
||||||
|
@ -13,7 +13,7 @@ except ModuleNotFoundError:
|
|||||||
if not TYPE_CHECKING:
|
if not TYPE_CHECKING:
|
||||||
import pydantic.main as pydantic_main
|
import pydantic.main as pydantic_main
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic.fields import ModelField # type: ignore
|
from pydantic.fields import ModelField
|
||||||
|
|
||||||
|
|
||||||
def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
|
def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None:
|
||||||
@ -44,13 +44,13 @@ def validate_field_name(bases: List[Type["BaseModel"]], field_name: str) -> None
|
|||||||
|
|
||||||
# monkeypatch pydantic validate_field_name method to skip validating
|
# monkeypatch pydantic validate_field_name method to skip validating
|
||||||
# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
|
# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True)
|
||||||
pydantic_main.validate_field_name = validate_field_name # type: ignore
|
pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPossiblyUnboundVariable, reportPrivateImportUsage]
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from reflex.vars import Var
|
from reflex.vars import Var
|
||||||
|
|
||||||
|
|
||||||
class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
class Base(BaseModel): # pyright: ignore [reportPossiblyUnboundVariable]
|
||||||
"""The base class subclassed by all Reflex classes.
|
"""The base class subclassed by all Reflex classes.
|
||||||
|
|
||||||
This class wraps Pydantic and provides common methods such as
|
This class wraps Pydantic and provides common methods such as
|
||||||
@ -75,12 +75,12 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|||||||
"""
|
"""
|
||||||
from reflex.utils.serializers import serialize
|
from reflex.utils.serializers import serialize
|
||||||
|
|
||||||
return self.__config__.json_dumps( # type: ignore
|
return self.__config__.json_dumps(
|
||||||
self.dict(),
|
self.dict(),
|
||||||
default=serialize,
|
default=serialize,
|
||||||
)
|
)
|
||||||
|
|
||||||
def set(self, **kwargs):
|
def set(self, **kwargs: Any):
|
||||||
"""Set multiple fields and return the object.
|
"""Set multiple fields and return the object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -113,12 +113,12 @@ class Base(BaseModel): # pyright: ignore [reportUnboundVariable]
|
|||||||
default_value: The default value of the field
|
default_value: The default value of the field
|
||||||
"""
|
"""
|
||||||
var_name = var._var_field_name
|
var_name = var._var_field_name
|
||||||
new_field = ModelField.infer(
|
new_field = ModelField.infer( # pyright: ignore [reportPossiblyUnboundVariable]
|
||||||
name=var_name,
|
name=var_name,
|
||||||
value=default_value,
|
value=default_value,
|
||||||
annotation=var._var_type,
|
annotation=var._var_type,
|
||||||
class_validators=None,
|
class_validators=None,
|
||||||
config=cls.__config__, # type: ignore
|
config=cls.__config__,
|
||||||
)
|
)
|
||||||
cls.__fields__.update({var_name: new_field})
|
cls.__fields__.update({var_name: new_field})
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Dict, Optional, Type, Union
|
from typing import Any, Callable, Dict, Optional, Type, Union
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
@ -12,7 +14,9 @@ from reflex.vars.base import Var
|
|||||||
try:
|
try:
|
||||||
from pydantic.v1.fields import ModelField
|
from pydantic.v1.fields import ModelField
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
from pydantic.fields import ModelField # type: ignore
|
from pydantic.fields import (
|
||||||
|
ModelField, # pyright: ignore [reportAttributeAccessIssue]
|
||||||
|
)
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.components.base import (
|
from reflex.components.base import (
|
||||||
@ -115,7 +119,7 @@ def compile_imports(import_dict: ParsedImportDict) -> list[dict]:
|
|||||||
default, rest = compile_import_statement(fields)
|
default, rest = compile_import_statement(fields)
|
||||||
|
|
||||||
# prevent lib from being rendered on the page if all imports are non rendered kind
|
# prevent lib from being rendered on the page if all imports are non rendered kind
|
||||||
if not any(f.render for f in fields): # type: ignore
|
if not any(f.render for f in fields):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not lib:
|
if not lib:
|
||||||
@ -163,8 +167,12 @@ def compile_state(state: Type[BaseState]) -> dict:
|
|||||||
try:
|
try:
|
||||||
initial_state = state(_reflex_internal_init=True).dict(initial=True)
|
initial_state = state(_reflex_internal_init=True).dict(initial=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
timestamp = datetime.now().strftime("%Y-%m-%d__%H-%M-%S")
|
||||||
|
constants.Reflex.LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
log_path = constants.Reflex.LOGS_DIR / f"state_compile_error_{timestamp}.log"
|
||||||
|
traceback.TracebackException.from_exception(e).print(file=log_path.open("w+"))
|
||||||
console.warn(
|
console.warn(
|
||||||
f"Failed to compile initial state with computed vars, excluding them: {e}"
|
f"Failed to compile initial state with computed vars. Error log saved to {log_path}"
|
||||||
)
|
)
|
||||||
initial_state = state(_reflex_internal_init=True).dict(
|
initial_state = state(_reflex_internal_init=True).dict(
|
||||||
initial=True, include_computed=False
|
initial=True, include_computed=False
|
||||||
@ -494,7 +502,7 @@ def empty_dir(path: str | Path, keep_files: list[str] | None = None):
|
|||||||
path_ops.rm(element)
|
path_ops.rm(element)
|
||||||
|
|
||||||
|
|
||||||
def is_valid_url(url) -> bool:
|
def is_valid_url(url: str) -> bool:
|
||||||
"""Check if a url is valid.
|
"""Check if a url is valid.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -31,7 +31,7 @@ class Bare(Component):
|
|||||||
return cls(contents=contents)
|
return cls(contents=contents)
|
||||||
else:
|
else:
|
||||||
contents = str(contents) if contents is not None else ""
|
contents = str(contents) if contents is not None else ""
|
||||||
return cls(contents=contents) # type: ignore
|
return cls(contents=contents)
|
||||||
|
|
||||||
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
|
||||||
"""Include the hooks for the component.
|
"""Include the hooks for the component.
|
||||||
|
@ -53,11 +53,11 @@ class Description(Meta):
|
|||||||
"""A component that displays the title of the current page."""
|
"""A component that displays the title of the current page."""
|
||||||
|
|
||||||
# The type of the description.
|
# The type of the description.
|
||||||
name: str = "description"
|
name: str | None = "description"
|
||||||
|
|
||||||
|
|
||||||
class Image(Meta):
|
class Image(Meta):
|
||||||
"""A component that displays the title of the current page."""
|
"""A component that displays the title of the current page."""
|
||||||
|
|
||||||
# The type of the image.
|
# The type of the image.
|
||||||
property: str = "og:image"
|
property: str | None = "og:image"
|
||||||
|
@ -23,8 +23,6 @@ from typing import (
|
|||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
from typing_extensions import deprecated
|
|
||||||
|
|
||||||
import reflex.state
|
import reflex.state
|
||||||
from reflex.base import Base
|
from reflex.base import Base
|
||||||
from reflex.compiler.templates import STATEFUL_COMPONENT
|
from reflex.compiler.templates import STATEFUL_COMPONENT
|
||||||
@ -47,11 +45,10 @@ from reflex.event import (
|
|||||||
EventChain,
|
EventChain,
|
||||||
EventHandler,
|
EventHandler,
|
||||||
EventSpec,
|
EventSpec,
|
||||||
EventVar,
|
|
||||||
no_args_event_spec,
|
no_args_event_spec,
|
||||||
)
|
)
|
||||||
from reflex.style import Style, format_as_emotion
|
from reflex.style import Style, format_as_emotion
|
||||||
from reflex.utils import console, format, imports, types
|
from reflex.utils import format, imports, types
|
||||||
from reflex.utils.imports import (
|
from reflex.utils.imports import (
|
||||||
ImmutableParsedImportDict,
|
ImmutableParsedImportDict,
|
||||||
ImportDict,
|
ImportDict,
|
||||||
@ -153,7 +150,7 @@ class BaseComponent(Base, ABC):
|
|||||||
class ComponentNamespace(SimpleNamespace):
|
class ComponentNamespace(SimpleNamespace):
|
||||||
"""A namespace to manage components with subcomponents."""
|
"""A namespace to manage components with subcomponents."""
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||||
"""Get the hash of the namespace.
|
"""Get the hash of the namespace.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -429,20 +426,22 @@ class Component(BaseComponent, ABC):
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
def determine_key(value: Any):
|
||||||
|
# Try to create a var from the value
|
||||||
|
key = value if isinstance(value, Var) else LiteralVar.create(value)
|
||||||
|
|
||||||
|
# Check that the var type is not None.
|
||||||
|
if key is None:
|
||||||
|
raise TypeError
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
# Check whether the key is a component prop.
|
# Check whether the key is a component prop.
|
||||||
if types._issubclass(field_type, Var):
|
if types._issubclass(field_type, Var):
|
||||||
# Used to store the passed types if var type is a union.
|
# Used to store the passed types if var type is a union.
|
||||||
passed_types = None
|
passed_types = None
|
||||||
try:
|
try:
|
||||||
# Try to create a var from the value.
|
kwargs[key] = determine_key(value)
|
||||||
if isinstance(value, Var):
|
|
||||||
kwargs[key] = value
|
|
||||||
else:
|
|
||||||
kwargs[key] = LiteralVar.create(value)
|
|
||||||
|
|
||||||
# Check that the var type is not None.
|
|
||||||
if kwargs[key] is None:
|
|
||||||
raise TypeError
|
|
||||||
|
|
||||||
expected_type = fields[key].outer_type_.__args__[0]
|
expected_type = fields[key].outer_type_.__args__[0]
|
||||||
# validate literal fields.
|
# validate literal fields.
|
||||||
@ -463,9 +462,7 @@ class Component(BaseComponent, ABC):
|
|||||||
if types.is_union(passed_type):
|
if types.is_union(passed_type):
|
||||||
# We need to check all possible types in the union.
|
# We need to check all possible types in the union.
|
||||||
passed_types = (
|
passed_types = (
|
||||||
arg
|
arg for arg in passed_type.__args__ if arg is not type(None)
|
||||||
for arg in passed_type.__args__ # type: ignore
|
|
||||||
if arg is not type(None)
|
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
# If the passed var is a union, check if all possible types are valid.
|
# If the passed var is a union, check if all possible types are valid.
|
||||||
@ -492,7 +489,7 @@ class Component(BaseComponent, ABC):
|
|||||||
# Check if the key is an event trigger.
|
# Check if the key is an event trigger.
|
||||||
if key in component_specific_triggers:
|
if key in component_specific_triggers:
|
||||||
kwargs["event_triggers"][key] = EventChain.create(
|
kwargs["event_triggers"][key] = EventChain.create(
|
||||||
value=value, # type: ignore
|
value=value,
|
||||||
args_spec=component_specific_triggers[key],
|
args_spec=component_specific_triggers[key],
|
||||||
key=key,
|
key=key,
|
||||||
)
|
)
|
||||||
@ -545,41 +542,6 @@ class Component(BaseComponent, ABC):
|
|||||||
# Construct the component.
|
# Construct the component.
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@deprecated("Use rx.EventChain.create instead.")
|
|
||||||
def _create_event_chain(
|
|
||||||
self,
|
|
||||||
args_spec: types.ArgsSpec | Sequence[types.ArgsSpec],
|
|
||||||
value: Union[
|
|
||||||
Var,
|
|
||||||
EventHandler,
|
|
||||||
EventSpec,
|
|
||||||
List[Union[EventHandler, EventSpec, EventVar]],
|
|
||||||
Callable,
|
|
||||||
],
|
|
||||||
key: Optional[str] = None,
|
|
||||||
) -> Union[EventChain, Var]:
|
|
||||||
"""Create an event chain from a variety of input types.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
args_spec: The args_spec of the event trigger being bound.
|
|
||||||
value: The value to create the event chain from.
|
|
||||||
key: The key of the event trigger being bound.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The event chain.
|
|
||||||
"""
|
|
||||||
console.deprecate(
|
|
||||||
"Component._create_event_chain",
|
|
||||||
"Use rx.EventChain.create instead.",
|
|
||||||
deprecation_version="0.6.8",
|
|
||||||
removal_version="0.7.0",
|
|
||||||
)
|
|
||||||
return EventChain.create(
|
|
||||||
value=value, # type: ignore
|
|
||||||
args_spec=args_spec,
|
|
||||||
key=key,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_event_triggers(
|
def get_event_triggers(
|
||||||
self,
|
self,
|
||||||
) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
|
) -> Dict[str, types.ArgsSpec | Sequence[types.ArgsSpec]]:
|
||||||
@ -614,7 +576,7 @@ class Component(BaseComponent, ABC):
|
|||||||
annotation = field.annotation
|
annotation = field.annotation
|
||||||
if (metadata := getattr(annotation, "__metadata__", None)) is not None:
|
if (metadata := getattr(annotation, "__metadata__", None)) is not None:
|
||||||
args_spec = metadata[0]
|
args_spec = metadata[0]
|
||||||
default_triggers[field.name] = args_spec or (no_args_event_spec) # type: ignore
|
default_triggers[field.name] = args_spec or (no_args_event_spec)
|
||||||
return default_triggers
|
return default_triggers
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@ -740,22 +702,21 @@ class Component(BaseComponent, ABC):
|
|||||||
# Import here to avoid circular imports.
|
# Import here to avoid circular imports.
|
||||||
from reflex.components.base.bare import Bare
|
from reflex.components.base.bare import Bare
|
||||||
from reflex.components.base.fragment import Fragment
|
from reflex.components.base.fragment import Fragment
|
||||||
from reflex.utils.exceptions import ComponentTypeError
|
from reflex.utils.exceptions import ChildrenTypeError
|
||||||
|
|
||||||
# Filter out None props
|
# Filter out None props
|
||||||
props = {key: value for key, value in props.items() if value is not None}
|
props = {key: value for key, value in props.items() if value is not None}
|
||||||
|
|
||||||
def validate_children(children):
|
def validate_children(children: tuple | list):
|
||||||
for child in children:
|
for child in children:
|
||||||
if isinstance(child, tuple):
|
if isinstance(child, (tuple, list)):
|
||||||
validate_children(child)
|
validate_children(child)
|
||||||
|
|
||||||
# Make sure the child is a valid type.
|
# Make sure the child is a valid type.
|
||||||
if not types._isinstance(child, ComponentChild):
|
if isinstance(child, dict) or not types._isinstance(
|
||||||
raise ComponentTypeError(
|
child, ComponentChild
|
||||||
"Children of Reflex components must be other components, "
|
):
|
||||||
"state vars, or primitive Python types. "
|
raise ChildrenTypeError(component=cls.__name__, child=child)
|
||||||
f"Got child {child} of type {type(child)}.",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate all the children.
|
# Validate all the children.
|
||||||
validate_children(children)
|
validate_children(children)
|
||||||
@ -798,7 +759,7 @@ class Component(BaseComponent, ABC):
|
|||||||
|
|
||||||
# Walk the MRO to call all `add_style` methods.
|
# Walk the MRO to call all `add_style` methods.
|
||||||
for base in self._iter_parent_classes_with_method("add_style"):
|
for base in self._iter_parent_classes_with_method("add_style"):
|
||||||
s = base.add_style(self) # type: ignore
|
s = base.add_style(self)
|
||||||
if s is not None:
|
if s is not None:
|
||||||
styles.append(s)
|
styles.append(s)
|
||||||
|
|
||||||
@ -890,7 +851,7 @@ class Component(BaseComponent, ABC):
|
|||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
|
|
||||||
def render(self) -> Dict:
|
def render(self) -> dict:
|
||||||
"""Render the component.
|
"""Render the component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -908,7 +869,7 @@ class Component(BaseComponent, ABC):
|
|||||||
self._replace_prop_names(rendered_dict)
|
self._replace_prop_names(rendered_dict)
|
||||||
return rendered_dict
|
return rendered_dict
|
||||||
|
|
||||||
def _replace_prop_names(self, rendered_dict) -> None:
|
def _replace_prop_names(self, rendered_dict: dict) -> None:
|
||||||
"""Replace the prop names in the render dictionary.
|
"""Replace the prop names in the render dictionary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -948,7 +909,7 @@ class Component(BaseComponent, ABC):
|
|||||||
comp.__name__ for comp in (Fragment, Foreach, Cond, Match)
|
comp.__name__ for comp in (Fragment, Foreach, Cond, Match)
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate_child(child):
|
def validate_child(child: Any):
|
||||||
child_name = type(child).__name__
|
child_name = type(child).__name__
|
||||||
|
|
||||||
# Iterate through the immediate children of fragment
|
# Iterate through the immediate children of fragment
|
||||||
@ -1711,7 +1672,7 @@ class CustomComponent(Component):
|
|||||||
if base_value is not None and isinstance(value, Component):
|
if base_value is not None and isinstance(value, Component):
|
||||||
self.component_props[key] = value
|
self.component_props[key] = value
|
||||||
value = base_value._replace(
|
value = base_value._replace(
|
||||||
merge_var_data=VarData( # type: ignore
|
merge_var_data=VarData(
|
||||||
imports=value._get_all_imports(),
|
imports=value._get_all_imports(),
|
||||||
hooks=value._get_all_hooks(),
|
hooks=value._get_all_hooks(),
|
||||||
)
|
)
|
||||||
@ -1744,7 +1705,7 @@ class CustomComponent(Component):
|
|||||||
return hash(self.tag)
|
return hash(self.tag)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_props(cls) -> Set[str]:
|
def get_props(cls) -> Set[str]: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||||
"""Get the props for the component.
|
"""Get the props for the component.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -1839,7 +1800,7 @@ class CustomComponent(Component):
|
|||||||
include_children=include_children, ignore_ids=ignore_ids
|
include_children=include_children, ignore_ids=ignore_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
@lru_cache(maxsize=None) # noqa
|
@lru_cache(maxsize=None) # noqa: B019
|
||||||
def get_component(self) -> Component:
|
def get_component(self) -> Component:
|
||||||
"""Render the component.
|
"""Render the component.
|
||||||
|
|
||||||
@ -2428,7 +2389,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
|||||||
if tag["name"] == "match":
|
if tag["name"] == "match":
|
||||||
element = tag["cond"]
|
element = tag["cond"]
|
||||||
|
|
||||||
conditionals = tag["default"]
|
conditionals = render_dict_to_var(tag["default"], imported_names)
|
||||||
|
|
||||||
for case in tag["match_cases"][::-1]:
|
for case in tag["match_cases"][::-1]:
|
||||||
condition = case[0].to_string() == element.to_string()
|
condition = case[0].to_string() == element.to_string()
|
||||||
@ -2437,7 +2398,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) ->
|
|||||||
|
|
||||||
conditionals = ternary_operation(
|
conditionals = ternary_operation(
|
||||||
condition,
|
condition,
|
||||||
case[-1],
|
render_dict_to_var(case[-1], imported_names),
|
||||||
conditionals,
|
conditionals,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ from reflex.vars.function import FunctionStringVar
|
|||||||
from reflex.vars.number import BooleanVar
|
from reflex.vars.number import BooleanVar
|
||||||
from reflex.vars.sequence import LiteralArrayVar
|
from reflex.vars.sequence import LiteralArrayVar
|
||||||
|
|
||||||
connect_error_var_data: VarData = VarData( # type: ignore
|
connect_error_var_data: VarData = VarData(
|
||||||
imports=Imports.EVENTS,
|
imports=Imports.EVENTS,
|
||||||
hooks={Hooks.EVENTS: None},
|
hooks={Hooks.EVENTS: None},
|
||||||
)
|
)
|
||||||
@ -99,14 +99,14 @@ class ConnectionToaster(Toaster):
|
|||||||
"""
|
"""
|
||||||
toast_id = "websocket-error"
|
toast_id = "websocket-error"
|
||||||
target_url = WebsocketTargetURL.create()
|
target_url = WebsocketTargetURL.create()
|
||||||
props = ToastProps( # type: ignore
|
props = ToastProps(
|
||||||
description=LiteralVar.create(
|
description=LiteralVar.create(
|
||||||
f"Check if server is reachable at {target_url}",
|
f"Check if server is reachable at {target_url}",
|
||||||
),
|
),
|
||||||
close_button=True,
|
close_button=True,
|
||||||
duration=120000,
|
duration=120000,
|
||||||
id=toast_id,
|
id=toast_id,
|
||||||
)
|
) # pyright: ignore [reportCallIssue]
|
||||||
|
|
||||||
individual_hooks = [
|
individual_hooks = [
|
||||||
f"const toast_props = {LiteralVar.create(props)!s};",
|
f"const toast_props = {LiteralVar.create(props)!s};",
|
||||||
@ -116,7 +116,7 @@ class ConnectionToaster(Toaster):
|
|||||||
_var_data=VarData(
|
_var_data=VarData(
|
||||||
imports={
|
imports={
|
||||||
"react": ["useEffect", "useState"],
|
"react": ["useEffect", "useState"],
|
||||||
**dict(target_url._get_all_var_data().imports), # type: ignore
|
**dict(target_url._get_all_var_data().imports), # pyright: ignore [reportArgumentType, reportOptionalMemberAccess]
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
).call(
|
).call(
|
||||||
|
@ -82,7 +82,9 @@ class Breakpoints(Dict[K, V]):
|
|||||||
return Breakpoints(
|
return Breakpoints(
|
||||||
{
|
{
|
||||||
k: v
|
k: v
|
||||||
for k, v in zip(["initial", *breakpoint_names], thresholds)
|
for k, v in zip(
|
||||||
|
["initial", *breakpoint_names], thresholds, strict=True
|
||||||
|
)
|
||||||
if v is not None
|
if v is not None
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -41,7 +41,7 @@ class ClientSideRouting(Component):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def wait_for_client_redirect(component) -> Component:
|
def wait_for_client_redirect(component: Component) -> Component:
|
||||||
"""Wait for a redirect to occur before rendering a component.
|
"""Wait for a redirect to occur before rendering a component.
|
||||||
|
|
||||||
This prevents the 404 page from flashing while the redirect is happening.
|
This prevents the 404 page from flashing while the redirect is happening.
|
||||||
|
@ -60,7 +60,7 @@ class ClientSideRouting(Component):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
def wait_for_client_redirect(component) -> Component: ...
|
def wait_for_client_redirect(component: Component) -> Component: ...
|
||||||
|
|
||||||
class Default404Page(Component):
|
class Default404Page(Component):
|
||||||
@overload
|
@overload
|
||||||
|
@ -26,10 +26,9 @@ class Cond(MemoizationLeaf):
|
|||||||
cond: Var[Any]
|
cond: Var[Any]
|
||||||
|
|
||||||
# The component to render if the cond is true.
|
# The component to render if the cond is true.
|
||||||
comp1: BaseComponent = None # type: ignore
|
comp1: BaseComponent | None = None
|
||||||
|
|
||||||
# The component to render if the cond is false.
|
# The component to render if the cond is false.
|
||||||
comp2: BaseComponent = None # type: ignore
|
comp2: BaseComponent | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
@ -73,8 +72,8 @@ class Cond(MemoizationLeaf):
|
|||||||
def _render(self) -> Tag:
|
def _render(self) -> Tag:
|
||||||
return CondTag(
|
return CondTag(
|
||||||
cond=self.cond,
|
cond=self.cond,
|
||||||
true_value=self.comp1.render(),
|
true_value=self.comp1.render(), # pyright: ignore [reportOptionalMemberAccess]
|
||||||
false_value=self.comp2.render(),
|
false_value=self.comp2.render(), # pyright: ignore [reportOptionalMemberAccess]
|
||||||
)
|
)
|
||||||
|
|
||||||
def render(self) -> Dict:
|
def render(self) -> Dict:
|
||||||
@ -111,7 +110,7 @@ class Cond(MemoizationLeaf):
|
|||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def cond(condition: Any, c1: Component, c2: Any) -> Component: ...
|
def cond(condition: Any, c1: Component, c2: Any) -> Component: ... # pyright: ignore [reportOverlappingOverload]
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -154,7 +153,7 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
|
|||||||
if c2 is None:
|
if c2 is None:
|
||||||
raise ValueError("For conditional vars, the second argument must be set.")
|
raise ValueError("For conditional vars, the second argument must be set.")
|
||||||
|
|
||||||
def create_var(cond_part):
|
def create_var(cond_part: Any) -> Var[Any]:
|
||||||
return LiteralVar.create(cond_part)
|
return LiteralVar.create(cond_part)
|
||||||
|
|
||||||
# convert the truth and false cond parts into vars so the _var_data can be obtained.
|
# convert the truth and false cond parts into vars so the _var_data can be obtained.
|
||||||
@ -163,16 +162,16 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
|
|||||||
|
|
||||||
# Create the conditional var.
|
# Create the conditional var.
|
||||||
return ternary_operation(
|
return ternary_operation(
|
||||||
cond_var.bool()._replace( # type: ignore
|
cond_var.bool()._replace(
|
||||||
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
|
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
|
||||||
), # type: ignore
|
),
|
||||||
c1,
|
c1,
|
||||||
c2,
|
c2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # type: ignore
|
def color_mode_cond(light: Component, dark: Component | None = None) -> Component: ... # pyright: ignore [reportOverlappingOverload]
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
@ -28,7 +28,7 @@ class DebounceInput(Component):
|
|||||||
min_length: Var[int]
|
min_length: Var[int]
|
||||||
|
|
||||||
# Time to wait between end of input and triggering on_change
|
# Time to wait between end of input and triggering on_change
|
||||||
debounce_timeout: Var[int] = DEFAULT_DEBOUNCE_TIMEOUT # type: ignore
|
debounce_timeout: Var[int] = Var.create(DEFAULT_DEBOUNCE_TIMEOUT)
|
||||||
|
|
||||||
# If true, notify when Enter key is pressed
|
# If true, notify when Enter key is pressed
|
||||||
force_notify_by_enter: Var[bool]
|
force_notify_by_enter: Var[bool]
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, Callable, Iterable
|
from typing import Any, Callable, Iterable
|
||||||
|
|
||||||
@ -97,9 +98,20 @@ class Foreach(Component):
|
|||||||
# Determine the index var name based on the params accepted by render_fn.
|
# Determine the index var name based on the params accepted by render_fn.
|
||||||
props["index_var_name"] = params[1].name
|
props["index_var_name"] = params[1].name
|
||||||
else:
|
else:
|
||||||
|
render_fn = self.render_fn
|
||||||
# Otherwise, use a deterministic index, based on the render function bytecode.
|
# Otherwise, use a deterministic index, based on the render function bytecode.
|
||||||
code_hash = (
|
code_hash = (
|
||||||
hash(self.render_fn.__code__)
|
hash(
|
||||||
|
getattr(
|
||||||
|
render_fn,
|
||||||
|
"__code__",
|
||||||
|
(
|
||||||
|
repr(self.render_fn)
|
||||||
|
if not isinstance(render_fn, functools.partial)
|
||||||
|
else render_fn.func.__code__
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
.to_bytes(
|
.to_bytes(
|
||||||
length=8,
|
length=8,
|
||||||
byteorder="big",
|
byteorder="big",
|
||||||
|
@ -14,7 +14,7 @@ class Html(Div):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# The HTML to render.
|
# The HTML to render.
|
||||||
dangerouslySetInnerHTML: Var[Dict[str, str]]
|
dangerouslySetInnerHTML: Var[Dict[str, str]] # noqa: N815
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children, **props):
|
def create(cls, *children, **props):
|
||||||
|
@ -109,7 +109,7 @@ class Match(MemoizationLeaf):
|
|||||||
return cases, default
|
return cases, default
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create_case_var_with_var_data(cls, case_element):
|
def _create_case_var_with_var_data(cls, case_element: Any) -> Var:
|
||||||
"""Convert a case element into a Var.If the case
|
"""Convert a case element into a Var.If the case
|
||||||
is a Style type, we extract the var data and merge it with the
|
is a Style type, we extract the var data and merge it with the
|
||||||
newly created Var.
|
newly created Var.
|
||||||
@ -222,7 +222,7 @@ class Match(MemoizationLeaf):
|
|||||||
cond=match_cond_var,
|
cond=match_cond_var,
|
||||||
match_cases=match_cases,
|
match_cases=match_cases,
|
||||||
default=default,
|
default=default,
|
||||||
children=[case[-1] for case in match_cases] + [default], # type: ignore
|
children=[case[-1] for case in match_cases] + [default], # pyright: ignore [reportArgumentType]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -236,13 +236,13 @@ class Match(MemoizationLeaf):
|
|||||||
_js_expr=format.format_match(
|
_js_expr=format.format_match(
|
||||||
cond=str(match_cond_var),
|
cond=str(match_cond_var),
|
||||||
match_cases=match_cases,
|
match_cases=match_cases,
|
||||||
default=default, # type: ignore
|
default=default, # pyright: ignore [reportArgumentType]
|
||||||
),
|
),
|
||||||
_var_type=default._var_type, # type: ignore
|
_var_type=default._var_type, # pyright: ignore [reportAttributeAccessIssue,reportOptionalMemberAccess]
|
||||||
_var_data=VarData.merge(
|
_var_data=VarData.merge(
|
||||||
match_cond_var._get_all_var_data(),
|
match_cond_var._get_all_var_data(),
|
||||||
*[el._get_all_var_data() for case in match_cases for el in case],
|
*[el._get_all_var_data() for case in match_cases for el in case],
|
||||||
default._get_all_var_data(), # type: ignore
|
default._get_all_var_data(), # pyright: ignore [reportAttributeAccessIssue, reportOptionalMemberAccess]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ class GhostUpload(Fragment):
|
|||||||
class Upload(MemoizationLeaf):
|
class Upload(MemoizationLeaf):
|
||||||
"""A file upload component."""
|
"""A file upload component."""
|
||||||
|
|
||||||
library = "react-dropzone@14.2.10"
|
library = "react-dropzone@14.3.5"
|
||||||
|
|
||||||
tag = ""
|
tag = ""
|
||||||
|
|
||||||
@ -269,7 +269,7 @@ class Upload(MemoizationLeaf):
|
|||||||
on_drop = upload_props["on_drop"]
|
on_drop = upload_props["on_drop"]
|
||||||
if isinstance(on_drop, Callable):
|
if isinstance(on_drop, Callable):
|
||||||
# Call the lambda to get the event chain.
|
# Call the lambda to get the event chain.
|
||||||
on_drop = call_event_fn(on_drop, _on_drop_spec) # type: ignore
|
on_drop = call_event_fn(on_drop, _on_drop_spec)
|
||||||
if isinstance(on_drop, EventSpec):
|
if isinstance(on_drop, EventSpec):
|
||||||
# Update the provided args for direct use with on_drop.
|
# Update the provided args for direct use with on_drop.
|
||||||
on_drop = on_drop.with_args(
|
on_drop = on_drop.with_args(
|
||||||
|
@ -14,7 +14,7 @@ from reflex.components.radix.themes.layout.box import Box
|
|||||||
from reflex.constants.colors import Color
|
from reflex.constants.colors import Color
|
||||||
from reflex.event import set_clipboard
|
from reflex.event import set_clipboard
|
||||||
from reflex.style import Style
|
from reflex.style import Style
|
||||||
from reflex.utils import console, format
|
from reflex.utils import format
|
||||||
from reflex.utils.imports import ImportVar
|
from reflex.utils.imports import ImportVar
|
||||||
from reflex.vars.base import LiteralVar, Var, VarData
|
from reflex.vars.base import LiteralVar, Var, VarData
|
||||||
|
|
||||||
@ -382,7 +382,7 @@ for theme_name in dir(Theme):
|
|||||||
class CodeBlock(Component, MarkdownComponentMap):
|
class CodeBlock(Component, MarkdownComponentMap):
|
||||||
"""A code block."""
|
"""A code block."""
|
||||||
|
|
||||||
library = "react-syntax-highlighter@15.6.0"
|
library = "react-syntax-highlighter@15.6.1"
|
||||||
|
|
||||||
tag = "PrismAsyncLight"
|
tag = "PrismAsyncLight"
|
||||||
|
|
||||||
@ -438,6 +438,8 @@ class CodeBlock(Component, MarkdownComponentMap):
|
|||||||
can_copy = props.pop("can_copy", False)
|
can_copy = props.pop("can_copy", False)
|
||||||
copy_button = props.pop("copy_button", None)
|
copy_button = props.pop("copy_button", None)
|
||||||
|
|
||||||
|
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
|
||||||
|
# themes respectively to ensure code compatibility.
|
||||||
if "theme" not in props:
|
if "theme" not in props:
|
||||||
# Default color scheme responds to global color mode.
|
# Default color scheme responds to global color mode.
|
||||||
props["theme"] = color_mode_cond(
|
props["theme"] = color_mode_cond(
|
||||||
@ -445,20 +447,9 @@ class CodeBlock(Component, MarkdownComponentMap):
|
|||||||
dark=Theme.one_dark,
|
dark=Theme.one_dark,
|
||||||
)
|
)
|
||||||
|
|
||||||
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
|
|
||||||
# themes respectively to ensure code compatibility.
|
|
||||||
if "theme" in props and not isinstance(props["theme"], Var):
|
|
||||||
props["theme"] = getattr(Theme, format.to_snake_case(props["theme"])) # type: ignore
|
|
||||||
console.deprecate(
|
|
||||||
feature_name="theme prop as string",
|
|
||||||
reason="Use code_block.themes instead.",
|
|
||||||
deprecation_version="0.6.0",
|
|
||||||
removal_version="0.7.0",
|
|
||||||
)
|
|
||||||
|
|
||||||
if can_copy:
|
if can_copy:
|
||||||
code = children[0]
|
code = children[0]
|
||||||
copy_button = ( # type: ignore
|
copy_button = (
|
||||||
copy_button
|
copy_button
|
||||||
if copy_button is not None
|
if copy_button is not None
|
||||||
else Button.create(
|
else Button.create(
|
||||||
|
@ -165,7 +165,7 @@ class DataEditor(NoSSRComponent):
|
|||||||
|
|
||||||
tag = "DataEditor"
|
tag = "DataEditor"
|
||||||
is_default = True
|
is_default = True
|
||||||
library: str = "@glideapps/glide-data-grid@^6.0.3"
|
library: str | None = "@glideapps/glide-data-grid@^6.0.3"
|
||||||
lib_dependencies: List[str] = [
|
lib_dependencies: List[str] = [
|
||||||
"lodash@^4.17.21",
|
"lodash@^4.17.21",
|
||||||
"react-responsive-carousel@^3.2.7",
|
"react-responsive-carousel@^3.2.7",
|
||||||
@ -321,6 +321,8 @@ class DataEditor(NoSSRComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
The import dict.
|
The import dict.
|
||||||
"""
|
"""
|
||||||
|
if self.library is None:
|
||||||
|
return {}
|
||||||
return {
|
return {
|
||||||
"": f"{format.format_library_name(self.library)}/dist/index.css",
|
"": f"{format.format_library_name(self.library)}/dist/index.css",
|
||||||
self.library: "GridCellKind",
|
self.library: "GridCellKind",
|
||||||
@ -343,7 +345,7 @@ class DataEditor(NoSSRComponent):
|
|||||||
data_callback = self.get_cell_content._js_expr
|
data_callback = self.get_cell_content._js_expr
|
||||||
else:
|
else:
|
||||||
data_callback = f"getData_{editor_id}"
|
data_callback = f"getData_{editor_id}"
|
||||||
self.get_cell_content = Var(_js_expr=data_callback) # type: ignore
|
self.get_cell_content = Var(_js_expr=data_callback)
|
||||||
|
|
||||||
code = [f"function {data_callback}([col, row])" "{"]
|
code = [f"function {data_callback}([col, row])" "{"]
|
||||||
|
|
||||||
|
@ -15,10 +15,8 @@ def svg_logo(color: Union[str, rx.Var[str]] = rx.color_mode_cond("#110F1F", "whi
|
|||||||
The Reflex logo SVG.
|
The Reflex logo SVG.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def logo_path(d):
|
def logo_path(d: str):
|
||||||
return rx.el.svg.path(
|
return rx.el.svg.path(d=d)
|
||||||
d=d,
|
|
||||||
)
|
|
||||||
|
|
||||||
paths = [
|
paths = [
|
||||||
"M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z",
|
"M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z",
|
||||||
|
@ -602,7 +602,7 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
|||||||
|
|
||||||
transformer_styles = {}
|
transformer_styles = {}
|
||||||
# Collect styles from transformers and wrapper
|
# Collect styles from transformers and wrapper
|
||||||
for transformer in code_block.transformers._var_value: # type: ignore
|
for transformer in code_block.transformers._var_value: # pyright: ignore [reportAttributeAccessIssue]
|
||||||
if isinstance(transformer, ShikiBaseTransformers) and transformer.style:
|
if isinstance(transformer, ShikiBaseTransformers) and transformer.style:
|
||||||
transformer_styles.update(transformer.style)
|
transformer_styles.update(transformer.style)
|
||||||
transformer_styles.update(code_wrapper_props.pop("style", {}))
|
transformer_styles.update(code_wrapper_props.pop("style", {}))
|
||||||
@ -653,8 +653,9 @@ class ShikiCodeBlock(Component, MarkdownComponentMap):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"the function names should be str names of functions in the specified transformer: {library!r}"
|
f"the function names should be str names of functions in the specified transformer: {library!r}"
|
||||||
)
|
)
|
||||||
return ShikiBaseTransformers( # type: ignore
|
return ShikiBaseTransformers(
|
||||||
library=library, fns=[FunctionStringVar.create(fn) for fn in fns]
|
library=library,
|
||||||
|
fns=[FunctionStringVar.create(fn) for fn in fns], # pyright: ignore [reportCallIssue]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _render(self, props: dict[str, Any] | None = None):
|
def _render(self, props: dict[str, Any] | None = None):
|
||||||
@ -757,13 +758,13 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
|
|||||||
|
|
||||||
if can_copy:
|
if can_copy:
|
||||||
code = children[0]
|
code = children[0]
|
||||||
copy_button = ( # type: ignore
|
copy_button = (
|
||||||
copy_button
|
copy_button
|
||||||
if copy_button is not None
|
if copy_button is not None
|
||||||
else Button.create(
|
else Button.create(
|
||||||
Icon.create(tag="copy", size=16, color=color("gray", 11)),
|
Icon.create(tag="copy", size=16, color=color("gray", 11)),
|
||||||
on_click=[
|
on_click=[
|
||||||
set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore
|
set_clipboard(cls._strip_transformer_triggers(code)),
|
||||||
copy_script(),
|
copy_script(),
|
||||||
],
|
],
|
||||||
style=Style(
|
style=Style(
|
||||||
|
@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Union
|
|||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.utils import imports
|
from reflex.utils import imports
|
||||||
from reflex.utils.exceptions import DynamicComponentMissingLibrary
|
from reflex.utils.exceptions import DynamicComponentMissingLibraryError
|
||||||
from reflex.utils.format import format_library_name
|
from reflex.utils.format import format_library_name
|
||||||
from reflex.utils.serializers import serializer
|
from reflex.utils.serializers import serializer
|
||||||
from reflex.vars import Var, get_unique_variable_name
|
from reflex.vars import Var, get_unique_variable_name
|
||||||
@ -36,13 +36,15 @@ def bundle_library(component: Union["Component", str]):
|
|||||||
component: The component to bundle the library with.
|
component: The component to bundle the library with.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
DynamicComponentMissingLibrary: Raised when a dynamic component is missing a library.
|
DynamicComponentMissingLibraryError: Raised when a dynamic component is missing a library.
|
||||||
"""
|
"""
|
||||||
if isinstance(component, str):
|
if isinstance(component, str):
|
||||||
bundled_libraries.add(component)
|
bundled_libraries.add(component)
|
||||||
return
|
return
|
||||||
if component.library is None:
|
if component.library is None:
|
||||||
raise DynamicComponentMissingLibrary("Component must have a library to bundle.")
|
raise DynamicComponentMissingLibraryError(
|
||||||
|
"Component must have a library to bundle."
|
||||||
|
)
|
||||||
bundled_libraries.add(format_library_name(component.library))
|
bundled_libraries.add(format_library_name(component.library))
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,4 +48,4 @@ PROP_TO_ELEMENTS = {
|
|||||||
ELEMENT_TO_PROPS = defaultdict(list)
|
ELEMENT_TO_PROPS = defaultdict(list)
|
||||||
for prop, elements in PROP_TO_ELEMENTS.items():
|
for prop, elements in PROP_TO_ELEMENTS.items():
|
||||||
for el in elements:
|
for el in elements:
|
||||||
ELEMENT_TO_PROPS[el].append(prop) # type: ignore
|
ELEMENT_TO_PROPS[el].append(prop)
|
||||||
|
@ -6,7 +6,7 @@ from reflex.components.component import Component
|
|||||||
class Element(Component):
|
class Element(Component):
|
||||||
"""The base class for all raw HTML elements."""
|
"""The base class for all raw HTML elements."""
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: object):
|
||||||
"""Two elements are equal if they have the same tag.
|
"""Two elements are equal if they have the same tag.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -102,7 +102,7 @@ class Fieldset(Element):
|
|||||||
name: Var[Union[str, int, bool]]
|
name: Var[Union[str, int, bool]]
|
||||||
|
|
||||||
|
|
||||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]:
|
||||||
"""Event handler spec for the on_submit event.
|
"""Event handler spec for the on_submit event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -111,7 +111,7 @@ def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]:
|
|||||||
return (FORM_DATA,)
|
return (FORM_DATA,)
|
||||||
|
|
||||||
|
|
||||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]:
|
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]:
|
||||||
"""Event handler spec for the on_submit event.
|
"""Event handler spec for the on_submit event.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -153,7 +153,7 @@ class Form(BaseHTML):
|
|||||||
target: Var[Union[str, int, bool]]
|
target: Var[Union[str, int, bool]]
|
||||||
|
|
||||||
# If true, the form will be cleared after submit.
|
# If true, the form will be cleared after submit.
|
||||||
reset_on_submit: Var[bool] = False # type: ignore
|
reset_on_submit: Var[bool] = Var.create(False)
|
||||||
|
|
||||||
# The name used to make this form's submit handler function unique.
|
# The name used to make this form's submit handler function unique.
|
||||||
handle_submit_unique_name: Var[str]
|
handle_submit_unique_name: Var[str]
|
||||||
@ -405,7 +405,7 @@ class Input(BaseHTML):
|
|||||||
(value_var := Var.create(value))._var_type
|
(value_var := Var.create(value))._var_type
|
||||||
):
|
):
|
||||||
props["value"] = ternary_operation(
|
props["value"] = ternary_operation(
|
||||||
(value_var != Var.create(None)) # pyright: ignore [reportGeneralTypeIssues]
|
(value_var != Var.create(None)) # pyright: ignore [reportArgumentType]
|
||||||
& (value_var != Var(_js_expr="undefined")),
|
& (value_var != Var(_js_expr="undefined")),
|
||||||
value,
|
value,
|
||||||
Var.create(""),
|
Var.create(""),
|
||||||
|
@ -270,8 +270,8 @@ class Fieldset(Element):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
def on_submit_event_spec() -> Tuple[Var[Dict[str, Any]]]: ...
|
def on_submit_event_spec() -> Tuple[Var[dict[str, Any]]]: ...
|
||||||
def on_submit_string_event_spec() -> Tuple[Var[Dict[str, str]]]: ...
|
def on_submit_string_event_spec() -> Tuple[Var[dict[str, str]]]: ...
|
||||||
|
|
||||||
class Form(BaseHTML):
|
class Form(BaseHTML):
|
||||||
@overload
|
@overload
|
||||||
@ -341,10 +341,10 @@ class Form(BaseHTML):
|
|||||||
on_submit: Optional[
|
on_submit: Optional[
|
||||||
Union[
|
Union[
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||||
],
|
],
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
|
@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
from reflex.components.component import Component
|
from reflex.components.component import Component
|
||||||
from reflex.utils import format
|
from reflex.utils import format
|
||||||
from reflex.vars.base import Var
|
from reflex.utils.imports import ImportVar
|
||||||
|
from reflex.vars.base import LiteralVar, Var
|
||||||
|
from reflex.vars.sequence import LiteralStringVar
|
||||||
|
|
||||||
|
|
||||||
class LucideIconComponent(Component):
|
class LucideIconComponent(Component):
|
||||||
"""Lucide Icon Component."""
|
"""Lucide Icon Component."""
|
||||||
|
|
||||||
library = "lucide-react@0.469.0"
|
library = "lucide-react@0.471.1"
|
||||||
|
|
||||||
|
|
||||||
class Icon(LucideIconComponent):
|
class Icon(LucideIconComponent):
|
||||||
@ -32,6 +34,7 @@ class Icon(LucideIconComponent):
|
|||||||
Raises:
|
Raises:
|
||||||
AttributeError: The errors tied to bad usage of the Icon component.
|
AttributeError: The errors tied to bad usage of the Icon component.
|
||||||
ValueError: If the icon tag is invalid.
|
ValueError: If the icon tag is invalid.
|
||||||
|
TypeError: If the icon name is not a string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The created component.
|
The created component.
|
||||||
@ -39,7 +42,6 @@ class Icon(LucideIconComponent):
|
|||||||
if children:
|
if children:
|
||||||
if len(children) == 1 and isinstance(children[0], str):
|
if len(children) == 1 and isinstance(children[0], str):
|
||||||
props["tag"] = children[0]
|
props["tag"] = children[0]
|
||||||
children = []
|
|
||||||
else:
|
else:
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
f"Passing multiple children to Icon component is not allowed: remove positional arguments {children[1:]} to fix"
|
f"Passing multiple children to Icon component is not allowed: remove positional arguments {children[1:]} to fix"
|
||||||
@ -47,24 +49,46 @@ class Icon(LucideIconComponent):
|
|||||||
if "tag" not in props:
|
if "tag" not in props:
|
||||||
raise AttributeError("Missing 'tag' keyword-argument for Icon")
|
raise AttributeError("Missing 'tag' keyword-argument for Icon")
|
||||||
|
|
||||||
|
tag: str | Var | LiteralVar = props.pop("tag")
|
||||||
|
if isinstance(tag, LiteralVar):
|
||||||
|
if isinstance(tag, LiteralStringVar):
|
||||||
|
tag = tag._var_value
|
||||||
|
else:
|
||||||
|
raise TypeError(f"Icon name must be a string, got {type(tag)}")
|
||||||
|
elif isinstance(tag, Var):
|
||||||
|
return DynamicIcon.create(name=tag, **props)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not isinstance(props["tag"], str)
|
not isinstance(tag, str)
|
||||||
or format.to_snake_case(props["tag"]) not in LUCIDE_ICON_LIST
|
or format.to_snake_case(tag) not in LUCIDE_ICON_LIST
|
||||||
):
|
):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid icon tag: {props['tag']}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..."
|
f"Invalid icon tag: {tag}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..."
|
||||||
"\nSee full list at https://lucide.dev/icons."
|
"\nSee full list at https://lucide.dev/icons."
|
||||||
)
|
)
|
||||||
|
|
||||||
if props["tag"] in LUCIDE_ICON_MAPPING_OVERRIDE:
|
if tag in LUCIDE_ICON_MAPPING_OVERRIDE:
|
||||||
props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[props["tag"]]
|
props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[tag]
|
||||||
else:
|
else:
|
||||||
props["tag"] = (
|
props["tag"] = format.to_title_case(format.to_snake_case(tag)) + "Icon"
|
||||||
format.to_title_case(format.to_snake_case(props["tag"])) + "Icon"
|
|
||||||
)
|
|
||||||
props["alias"] = f"Lucide{props['tag']}"
|
props["alias"] = f"Lucide{props['tag']}"
|
||||||
props.setdefault("color", "var(--current-color)")
|
props.setdefault("color", "var(--current-color)")
|
||||||
return super().create(*children, **props)
|
return super().create(**props)
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicIcon(LucideIconComponent):
|
||||||
|
"""A DynamicIcon component."""
|
||||||
|
|
||||||
|
tag = "DynamicIcon"
|
||||||
|
|
||||||
|
name: Var[str]
|
||||||
|
|
||||||
|
def _get_imports(self):
|
||||||
|
_imports = super()._get_imports()
|
||||||
|
if self.library:
|
||||||
|
_imports.pop(self.library)
|
||||||
|
_imports["lucide-react/dynamic"] = [ImportVar("DynamicIcon", install=False)]
|
||||||
|
return _imports
|
||||||
|
|
||||||
|
|
||||||
LUCIDE_ICON_LIST = [
|
LUCIDE_ICON_LIST = [
|
||||||
@ -846,6 +870,7 @@ LUCIDE_ICON_LIST = [
|
|||||||
"house",
|
"house",
|
||||||
"house_plug",
|
"house_plug",
|
||||||
"house_plus",
|
"house_plus",
|
||||||
|
"house_wifi",
|
||||||
"ice_cream_bowl",
|
"ice_cream_bowl",
|
||||||
"ice_cream_cone",
|
"ice_cream_cone",
|
||||||
"id_card",
|
"id_card",
|
||||||
@ -1534,6 +1559,7 @@ LUCIDE_ICON_LIST = [
|
|||||||
"trending_up_down",
|
"trending_up_down",
|
||||||
"triangle",
|
"triangle",
|
||||||
"triangle_alert",
|
"triangle_alert",
|
||||||
|
"triangle_dashed",
|
||||||
"triangle_right",
|
"triangle_right",
|
||||||
"trophy",
|
"trophy",
|
||||||
"truck",
|
"truck",
|
||||||
|
@ -104,12 +104,60 @@ class Icon(LucideIconComponent):
|
|||||||
Raises:
|
Raises:
|
||||||
AttributeError: The errors tied to bad usage of the Icon component.
|
AttributeError: The errors tied to bad usage of the Icon component.
|
||||||
ValueError: If the icon tag is invalid.
|
ValueError: If the icon tag is invalid.
|
||||||
|
TypeError: If the icon name is not a string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The created component.
|
The created component.
|
||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
class DynamicIcon(LucideIconComponent):
|
||||||
|
@overload
|
||||||
|
@classmethod
|
||||||
|
def create( # type: ignore
|
||||||
|
cls,
|
||||||
|
*children,
|
||||||
|
name: Optional[Union[Var[str], str]] = 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, Any]]] = None,
|
||||||
|
on_blur: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_click: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_focus: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mount: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
**props,
|
||||||
|
) -> "DynamicIcon":
|
||||||
|
"""Create the component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The children of the component.
|
||||||
|
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 of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The component.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
LUCIDE_ICON_LIST = [
|
LUCIDE_ICON_LIST = [
|
||||||
"a_arrow_down",
|
"a_arrow_down",
|
||||||
"a_arrow_up",
|
"a_arrow_up",
|
||||||
@ -889,6 +937,7 @@ LUCIDE_ICON_LIST = [
|
|||||||
"house",
|
"house",
|
||||||
"house_plug",
|
"house_plug",
|
||||||
"house_plus",
|
"house_plus",
|
||||||
|
"house_wifi",
|
||||||
"ice_cream_bowl",
|
"ice_cream_bowl",
|
||||||
"ice_cream_cone",
|
"ice_cream_cone",
|
||||||
"id_card",
|
"id_card",
|
||||||
@ -1577,6 +1626,7 @@ LUCIDE_ICON_LIST = [
|
|||||||
"trending_up_down",
|
"trending_up_down",
|
||||||
"triangle",
|
"triangle",
|
||||||
"triangle_alert",
|
"triangle_alert",
|
||||||
|
"triangle_dashed",
|
||||||
"triangle_right",
|
"triangle_right",
|
||||||
"trophy",
|
"trophy",
|
||||||
"truck",
|
"truck",
|
||||||
|
@ -8,7 +8,7 @@ from functools import lru_cache
|
|||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from typing import Any, Callable, Dict, Sequence, Union
|
from typing import Any, Callable, Dict, Sequence, Union
|
||||||
|
|
||||||
from reflex.components.component import Component, CustomComponent
|
from reflex.components.component import BaseComponent, Component, CustomComponent
|
||||||
from reflex.components.tags.tag import Tag
|
from reflex.components.tags.tag import Tag
|
||||||
from reflex.utils import types
|
from reflex.utils import types
|
||||||
from reflex.utils.imports import ImportDict, ImportVar
|
from reflex.utils.imports import ImportDict, ImportVar
|
||||||
@ -65,8 +65,8 @@ def get_base_component_map() -> dict[str, Callable]:
|
|||||||
"h5": lambda value: Heading.create(value, as_="h5", size="2", margin_y="0.5em"),
|
"h5": lambda value: Heading.create(value, as_="h5", size="2", margin_y="0.5em"),
|
||||||
"h6": lambda value: Heading.create(value, as_="h6", size="1", margin_y="0.5em"),
|
"h6": lambda value: Heading.create(value, as_="h6", size="1", margin_y="0.5em"),
|
||||||
"p": lambda value: Text.create(value, margin_y="1em"),
|
"p": lambda value: Text.create(value, margin_y="1em"),
|
||||||
"ul": lambda value: UnorderedList.create(value, margin_y="1em"), # type: ignore
|
"ul": lambda value: UnorderedList.create(value, margin_y="1em"),
|
||||||
"ol": lambda value: OrderedList.create(value, margin_y="1em"), # type: ignore
|
"ol": lambda value: OrderedList.create(value, margin_y="1em"),
|
||||||
"li": lambda value: ListItem.create(value, margin_y="0.5em"),
|
"li": lambda value: ListItem.create(value, margin_y="0.5em"),
|
||||||
"a": lambda value: Link.create(value),
|
"a": lambda value: Link.create(value),
|
||||||
"code": lambda value: Code.create(value),
|
"code": lambda value: Code.create(value),
|
||||||
@ -236,7 +236,7 @@ class Markdown(Component):
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
*[
|
*[
|
||||||
component(_MOCK_ARG)._get_all_imports() # type: ignore
|
component(_MOCK_ARG)._get_all_imports()
|
||||||
for component in self.component_map.values()
|
for component in self.component_map.values()
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
@ -327,7 +327,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
|||||||
if tag != "codeblock"
|
if tag != "codeblock"
|
||||||
# For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string.
|
# For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string.
|
||||||
else ternary_operation(
|
else ternary_operation(
|
||||||
ARRAY_ISARRAY.call(_CHILDREN), # type: ignore
|
ARRAY_ISARRAY.call(_CHILDREN), # pyright: ignore [reportArgumentType]
|
||||||
_CHILDREN.to(list).join("\n"),
|
_CHILDREN.to(list).join("\n"),
|
||||||
_CHILDREN,
|
_CHILDREN,
|
||||||
).to(str)
|
).to(str)
|
||||||
@ -379,7 +379,9 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
|||||||
# fallback to the default fn Var creation if the component is not a MarkdownComponentMap.
|
# fallback to the default fn Var creation if the component is not a MarkdownComponentMap.
|
||||||
return MarkdownComponentMap.create_map_fn_var(fn_body=formatted_component)
|
return MarkdownComponentMap.create_map_fn_var(fn_body=formatted_component)
|
||||||
|
|
||||||
def _get_map_fn_custom_code_from_children(self, component) -> list[str]:
|
def _get_map_fn_custom_code_from_children(
|
||||||
|
self, component: BaseComponent
|
||||||
|
) -> list[str]:
|
||||||
"""Recursively get markdown custom code from children components.
|
"""Recursively get markdown custom code from children components.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -409,7 +411,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
|||||||
return custom_code_list
|
return custom_code_list
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _component_map_hash(component_map) -> str:
|
def _component_map_hash(component_map: dict) -> str:
|
||||||
inp = str(
|
inp = str(
|
||||||
{tag: component(_MOCK_ARG) for tag, component in component_map.items()}
|
{tag: component(_MOCK_ARG) for tag, component in component_map.items()}
|
||||||
).encode()
|
).encode()
|
||||||
@ -425,7 +427,7 @@ const {_LANGUAGE!s} = match ? match[1] : '';
|
|||||||
for _component in self.component_map.values():
|
for _component in self.component_map.values():
|
||||||
comp = _component(_MOCK_ARG)
|
comp = _component(_MOCK_ARG)
|
||||||
hooks.update(comp._get_all_hooks())
|
hooks.update(comp._get_all_hooks())
|
||||||
formatted_hooks = MACROS.module.renderHooks(hooks) # type: ignore
|
formatted_hooks = MACROS.module.renderHooks(hooks) # pyright: ignore [reportAttributeAccessIssue]
|
||||||
return f"""
|
return f"""
|
||||||
function {self._get_component_map_name()} () {{
|
function {self._get_component_map_name()} () {{
|
||||||
{formatted_hooks}
|
{formatted_hooks}
|
||||||
|
@ -28,9 +28,9 @@ class MomentDelta:
|
|||||||
class Moment(NoSSRComponent):
|
class Moment(NoSSRComponent):
|
||||||
"""The Moment component."""
|
"""The Moment component."""
|
||||||
|
|
||||||
tag: str = "Moment"
|
tag: str | None = "Moment"
|
||||||
is_default = True
|
is_default = True
|
||||||
library: str = "react-moment"
|
library: str | None = "react-moment"
|
||||||
lib_dependencies: List[str] = ["moment"]
|
lib_dependencies: List[str] = ["moment"]
|
||||||
|
|
||||||
# How often the date update (how often time update / 0 to disable).
|
# How often the date update (how often time update / 0 to disable).
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
"""Image component from next/image."""
|
"""Image component from next/image."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Literal, Optional, Union
|
from typing import Any, Literal, Optional, Union
|
||||||
|
|
||||||
from reflex.event import EventHandler, no_args_event_spec
|
from reflex.event import EventHandler, no_args_event_spec
|
||||||
from reflex.utils import types
|
from reflex.utils import console, types
|
||||||
from reflex.vars.base import Var
|
from reflex.vars.base import Var
|
||||||
|
|
||||||
from .base import NextComponent
|
from .base import NextComponent
|
||||||
|
|
||||||
|
DEFAULT_W_H = "100%"
|
||||||
|
|
||||||
|
|
||||||
class Image(NextComponent):
|
class Image(NextComponent):
|
||||||
"""Display an image."""
|
"""Display an image."""
|
||||||
@ -53,7 +57,7 @@ class Image(NextComponent):
|
|||||||
loading: Var[Literal["lazy", "eager"]]
|
loading: Var[Literal["lazy", "eager"]]
|
||||||
|
|
||||||
# A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
|
# A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
|
||||||
blurDataURL: Var[str]
|
blur_data_url: Var[str]
|
||||||
|
|
||||||
# Fires when the image has loaded.
|
# Fires when the image has loaded.
|
||||||
on_load: EventHandler[no_args_event_spec]
|
on_load: EventHandler[no_args_event_spec]
|
||||||
@ -80,10 +84,18 @@ class Image(NextComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
_type_: _description_
|
_type_: _description_
|
||||||
"""
|
"""
|
||||||
style = props.get("style", {})
|
if "blurDataURL" in props:
|
||||||
DEFAULT_W_H = "100%"
|
console.deprecate(
|
||||||
|
feature_name="blurDataURL",
|
||||||
|
reason="Use blur_data_url instead",
|
||||||
|
deprecation_version="0.7.0",
|
||||||
|
removal_version="0.8.0",
|
||||||
|
)
|
||||||
|
props["blur_data_url"] = props.pop("blurDataURL")
|
||||||
|
|
||||||
def check_prop_type(prop_name, prop_value):
|
style = props.get("style", {})
|
||||||
|
|
||||||
|
def check_prop_type(prop_name: str, prop_value: int | str | None):
|
||||||
if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]):
|
if types.check_prop_in_allowed_types(prop_value, allowed_types=[int]):
|
||||||
props[prop_name] = prop_value
|
props[prop_name] = prop_value
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ from reflex.vars.base import Var
|
|||||||
|
|
||||||
from .base import NextComponent
|
from .base import NextComponent
|
||||||
|
|
||||||
|
DEFAULT_W_H = "100%"
|
||||||
|
|
||||||
class Image(NextComponent):
|
class Image(NextComponent):
|
||||||
@overload
|
@overload
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -30,7 +32,7 @@ class Image(NextComponent):
|
|||||||
loading: Optional[
|
loading: Optional[
|
||||||
Union[Literal["eager", "lazy"], Var[Literal["eager", "lazy"]]]
|
Union[Literal["eager", "lazy"], Var[Literal["eager", "lazy"]]]
|
||||||
] = None,
|
] = None,
|
||||||
blurDataURL: Optional[Union[Var[str], str]] = None,
|
blur_data_url: Optional[Union[Var[str], str]] = None,
|
||||||
style: Optional[Style] = None,
|
style: Optional[Style] = None,
|
||||||
key: Optional[Any] = None,
|
key: Optional[Any] = None,
|
||||||
id: Optional[Any] = None,
|
id: Optional[Any] = None,
|
||||||
@ -71,7 +73,7 @@ class Image(NextComponent):
|
|||||||
priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
|
priority: When true, the image will be considered high priority and preload. Lazy loading is automatically disabled for images using priority.
|
||||||
placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
|
placeholder: A placeholder to use while the image is loading. Possible values are blur, empty, or data:image/.... Defaults to empty.
|
||||||
loading: The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
|
loading: The loading behavior of the image. Defaults to lazy. Can hurt performance, recommended to use `priority` instead.
|
||||||
blurDataURL: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
|
blur_data_url: A Data URL to be used as a placeholder image before the src image successfully loads. Only takes effect when combined with placeholder="blur".
|
||||||
on_load: Fires when the image has loaded.
|
on_load: Fires when the image has loaded.
|
||||||
on_error: Fires when the image has an error.
|
on_error: Fires when the image has an error.
|
||||||
style: The style of the component.
|
style: The style of the component.
|
||||||
|
@ -17,4 +17,4 @@ class NextLink(Component):
|
|||||||
href: Var[str]
|
href: Var[str]
|
||||||
|
|
||||||
# Whether to pass the href prop to the child.
|
# Whether to pass the href prop to the child.
|
||||||
pass_href: Var[bool] = True # type: ignore
|
pass_href: Var[bool] = Var.create(True)
|
||||||
|
@ -18,8 +18,8 @@ try:
|
|||||||
Template = layout.Template
|
Template = layout.Template
|
||||||
except ImportError:
|
except ImportError:
|
||||||
console.warn("Plotly is not installed. Please run `pip install plotly`.")
|
console.warn("Plotly is not installed. Please run `pip install plotly`.")
|
||||||
Figure = Any # type: ignore
|
Figure = Any
|
||||||
Template = Any # type: ignore
|
Template = Any
|
||||||
|
|
||||||
|
|
||||||
def _event_points_data_signature(e0: Var) -> Tuple[Var[List[Point]]]:
|
def _event_points_data_signature(e0: Var) -> Tuple[Var[List[Point]]]:
|
||||||
@ -95,20 +95,20 @@ class Plotly(NoSSRComponent):
|
|||||||
|
|
||||||
library = "react-plotly.js@2.6.0"
|
library = "react-plotly.js@2.6.0"
|
||||||
|
|
||||||
lib_dependencies: List[str] = ["plotly.js@2.35.2"]
|
lib_dependencies: List[str] = ["plotly.js@2.35.3"]
|
||||||
|
|
||||||
tag = "Plot"
|
tag = "Plot"
|
||||||
|
|
||||||
is_default = True
|
is_default = True
|
||||||
|
|
||||||
# The figure to display. This can be a plotly figure or a plotly data json.
|
# The figure to display. This can be a plotly figure or a plotly data json.
|
||||||
data: Var[Figure] # type: ignore
|
data: Var[Figure] # pyright: ignore [reportInvalidTypeForm]
|
||||||
|
|
||||||
# The layout of the graph.
|
# The layout of the graph.
|
||||||
layout: Var[Dict]
|
layout: Var[Dict]
|
||||||
|
|
||||||
# The template for visual appearance of the graph.
|
# The template for visual appearance of the graph.
|
||||||
template: Var[Template] # type: ignore
|
template: Var[Template] # pyright: ignore [reportInvalidTypeForm]
|
||||||
|
|
||||||
# The config of the graph.
|
# The config of the graph.
|
||||||
config: Var[Dict]
|
config: Var[Dict]
|
||||||
|
@ -48,7 +48,7 @@ class PropsBase(Base):
|
|||||||
class NoExtrasAllowedProps(Base):
|
class NoExtrasAllowedProps(Base):
|
||||||
"""A class that holds props to be passed or applied to a component with no extra props allowed."""
|
"""A class that holds props to be passed or applied to a component with no extra props allowed."""
|
||||||
|
|
||||||
def __init__(self, component_name=None, **kwargs):
|
def __init__(self, component_name: str | None = None, **kwargs):
|
||||||
"""Initialize the props.
|
"""Initialize the props.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -62,13 +62,13 @@ class NoExtrasAllowedProps(Base):
|
|||||||
try:
|
try:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
invalid_fields = ", ".join([error["loc"][0] for error in e.errors()]) # type: ignore
|
invalid_fields = ", ".join([error["loc"][0] for error in e.errors()]) # pyright: ignore [reportCallIssue, reportArgumentType]
|
||||||
supported_props_str = ", ".join(f'"{field}"' for field in self.get_fields())
|
supported_props_str = ", ".join(f'"{field}"' for field in self.get_fields())
|
||||||
raise InvalidPropValueError(
|
raise InvalidPropValueError(
|
||||||
f"Invalid prop(s) {invalid_fields} for {component_name!r}. Supported props are {supported_props_str}"
|
f"Invalid prop(s) {invalid_fields} for {component_name!r}. Supported props are {supported_props_str}"
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
class Config:
|
class Config: # pyright: ignore [reportIncompatibleVariableOverride]
|
||||||
"""Pydantic config."""
|
"""Pydantic config."""
|
||||||
|
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
@ -55,7 +55,7 @@ from .themes.layout.container import container as container
|
|||||||
from .themes.layout.flex import flex as flex
|
from .themes.layout.flex import flex as flex
|
||||||
from .themes.layout.grid import grid as grid
|
from .themes.layout.grid import grid as grid
|
||||||
from .themes.layout.list import list_item as list_item
|
from .themes.layout.list import list_item as list_item
|
||||||
from .themes.layout.list import list_ns as list # noqa
|
from .themes.layout.list import list_ns as list # noqa: F401
|
||||||
from .themes.layout.list import ordered_list as ordered_list
|
from .themes.layout.list import ordered_list as ordered_list
|
||||||
from .themes.layout.list import unordered_list as unordered_list
|
from .themes.layout.list import unordered_list as unordered_list
|
||||||
from .themes.layout.section import section as section
|
from .themes.layout.section import section as section
|
||||||
|
@ -196,8 +196,9 @@ class AccordionItem(AccordionComponent):
|
|||||||
|
|
||||||
# The header of the accordion item.
|
# The header of the accordion item.
|
||||||
header: Var[Union[Component, str]]
|
header: Var[Union[Component, str]]
|
||||||
|
|
||||||
# The content of the accordion item.
|
# The content of the accordion item.
|
||||||
content: Var[Union[Component, str]] = Var.create(None)
|
content: Var[Union[Component, str, None]] = Var.create(None)
|
||||||
|
|
||||||
_valid_children: List[str] = [
|
_valid_children: List[str] = [
|
||||||
"AccordionHeader",
|
"AccordionHeader",
|
||||||
@ -485,11 +486,11 @@ to {
|
|||||||
Returns:
|
Returns:
|
||||||
The style of the component.
|
The style of the component.
|
||||||
"""
|
"""
|
||||||
slideDown = LiteralVar.create(
|
slide_down = LiteralVar.create(
|
||||||
"${slideDown} var(--animation-duration) var(--animation-easing)",
|
"${slideDown} var(--animation-duration) var(--animation-easing)",
|
||||||
)
|
)
|
||||||
|
|
||||||
slideUp = LiteralVar.create(
|
slide_up = LiteralVar.create(
|
||||||
"${slideUp} var(--animation-duration) var(--animation-easing)",
|
"${slideUp} var(--animation-duration) var(--animation-easing)",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -503,8 +504,8 @@ to {
|
|||||||
"display": "block",
|
"display": "block",
|
||||||
"height": "var(--space-3)",
|
"height": "var(--space-3)",
|
||||||
},
|
},
|
||||||
"&[data-state='open']": {"animation": slideDown},
|
"&[data-state='open']": {"animation": slide_down},
|
||||||
"&[data-state='closed']": {"animation": slideUp},
|
"&[data-state='closed']": {"animation": slide_up},
|
||||||
_inherited_variant_selector("classic"): {
|
_inherited_variant_selector("classic"): {
|
||||||
"color": "var(--accent-contrast)",
|
"color": "var(--accent-contrast)",
|
||||||
},
|
},
|
||||||
|
@ -308,7 +308,9 @@ class AccordionItem(AccordionComponent):
|
|||||||
value: Optional[Union[Var[str], str]] = None,
|
value: Optional[Union[Var[str], str]] = None,
|
||||||
disabled: Optional[Union[Var[bool], bool]] = None,
|
disabled: Optional[Union[Var[bool], bool]] = None,
|
||||||
header: Optional[Union[Component, Var[Union[Component, str]], str]] = None,
|
header: Optional[Union[Component, Var[Union[Component, str]], str]] = None,
|
||||||
content: Optional[Union[Component, Var[Union[Component, str]], str]] = None,
|
content: Optional[
|
||||||
|
Union[Component, Var[Optional[Union[Component, str]]], str]
|
||||||
|
] = None,
|
||||||
color_scheme: Optional[
|
color_scheme: Optional[
|
||||||
Union[
|
Union[
|
||||||
Literal[
|
Literal[
|
||||||
|
@ -66,7 +66,7 @@ class DrawerRoot(DrawerComponent):
|
|||||||
scroll_lock_timeout: Var[int]
|
scroll_lock_timeout: Var[int]
|
||||||
|
|
||||||
# When `True`, it prevents scroll restoration. Defaults to `True`.
|
# When `True`, it prevents scroll restoration. Defaults to `True`.
|
||||||
preventScrollRestoration: Var[bool]
|
prevent_scroll_restoration: Var[bool]
|
||||||
|
|
||||||
# Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
# Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
||||||
should_scale_background: Var[bool]
|
should_scale_background: Var[bool]
|
||||||
@ -83,7 +83,7 @@ class DrawerTrigger(DrawerComponent):
|
|||||||
alias = "Vaul" + tag
|
alias = "Vaul" + tag
|
||||||
|
|
||||||
# Defaults to true, if the first child acts as the trigger.
|
# Defaults to true, if the first child acts as the trigger.
|
||||||
as_child: Var[bool] = True # type: ignore
|
as_child: Var[bool] = Var.create(True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children: Any, **props: Any) -> Component:
|
def create(cls, *children: Any, **props: Any) -> Component:
|
||||||
|
@ -81,7 +81,7 @@ class DrawerRoot(DrawerComponent):
|
|||||||
snap_points: Optional[List[Union[float, str]]] = None,
|
snap_points: Optional[List[Union[float, str]]] = None,
|
||||||
fade_from_index: Optional[Union[Var[int], int]] = None,
|
fade_from_index: Optional[Union[Var[int], int]] = None,
|
||||||
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
|
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
|
||||||
preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
|
prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
|
||||||
should_scale_background: Optional[Union[Var[bool], bool]] = None,
|
should_scale_background: Optional[Union[Var[bool], bool]] = None,
|
||||||
close_threshold: Optional[Union[Var[float], float]] = None,
|
close_threshold: Optional[Union[Var[float], float]] = None,
|
||||||
as_child: Optional[Union[Var[bool], bool]] = None,
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
@ -129,7 +129,7 @@ class DrawerRoot(DrawerComponent):
|
|||||||
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
|
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
|
||||||
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
|
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
|
||||||
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
|
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
|
||||||
preventScrollRestoration: When `True`, it prevents scroll restoration. Defaults to `True`.
|
prevent_scroll_restoration: When `True`, it prevents scroll restoration. Defaults to `True`.
|
||||||
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
||||||
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
|
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
|
||||||
as_child: Change the default rendered element for the one passed as a child.
|
as_child: Change the default rendered element for the one passed as a child.
|
||||||
@ -567,7 +567,7 @@ class Drawer(ComponentNamespace):
|
|||||||
snap_points: Optional[List[Union[float, str]]] = None,
|
snap_points: Optional[List[Union[float, str]]] = None,
|
||||||
fade_from_index: Optional[Union[Var[int], int]] = None,
|
fade_from_index: Optional[Union[Var[int], int]] = None,
|
||||||
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
|
scroll_lock_timeout: Optional[Union[Var[int], int]] = None,
|
||||||
preventScrollRestoration: Optional[Union[Var[bool], bool]] = None,
|
prevent_scroll_restoration: Optional[Union[Var[bool], bool]] = None,
|
||||||
should_scale_background: Optional[Union[Var[bool], bool]] = None,
|
should_scale_background: Optional[Union[Var[bool], bool]] = None,
|
||||||
close_threshold: Optional[Union[Var[float], float]] = None,
|
close_threshold: Optional[Union[Var[float], float]] = None,
|
||||||
as_child: Optional[Union[Var[bool], bool]] = None,
|
as_child: Optional[Union[Var[bool], bool]] = None,
|
||||||
@ -615,7 +615,7 @@ class Drawer(ComponentNamespace):
|
|||||||
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
|
snap_points: Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Also Accept px values, which doesn't take screen height into account.
|
||||||
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
|
fade_from_index: Index of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
|
||||||
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
|
scroll_lock_timeout: Duration for which the drawer is not draggable after scrolling content inside of the drawer. Defaults to 500ms
|
||||||
preventScrollRestoration: When `True`, it prevents scroll restoration. Defaults to `True`.
|
prevent_scroll_restoration: When `True`, it prevents scroll restoration. Defaults to `True`.
|
||||||
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
should_scale_background: Enable background scaling, it requires container element with `vaul-drawer-wrapper` attribute to scale its background.
|
||||||
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
|
close_threshold: Number between 0 and 1 that determines when the drawer should be closed.
|
||||||
as_child: Change the default rendered element for the one passed as a child.
|
as_child: Change the default rendered element for the one passed as a child.
|
||||||
|
@ -132,10 +132,10 @@ class FormRoot(FormComponent, HTMLForm):
|
|||||||
on_submit: Optional[
|
on_submit: Optional[
|
||||||
Union[
|
Union[
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||||
],
|
],
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
@ -608,10 +608,10 @@ class Form(FormRoot):
|
|||||||
on_submit: Optional[
|
on_submit: Optional[
|
||||||
Union[
|
Union[
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||||
],
|
],
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
@ -741,10 +741,10 @@ class FormNamespace(ComponentNamespace):
|
|||||||
on_submit: Optional[
|
on_submit: Optional[
|
||||||
Union[
|
Union[
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, Any]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, Any]], BASE_STATE]
|
||||||
],
|
],
|
||||||
Union[
|
Union[
|
||||||
EventType[[], BASE_STATE], EventType[[Dict[str, str]], BASE_STATE]
|
EventType[[], BASE_STATE], EventType[[dict[str, str]], BASE_STATE]
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
|
@ -83,7 +83,7 @@ class ProgressIndicator(ProgressComponent):
|
|||||||
"&[data_state='loading']": {
|
"&[data_state='loading']": {
|
||||||
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
|
"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
|
||||||
},
|
},
|
||||||
"transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))", # type: ignore
|
"transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))",
|
||||||
"boxShadow": "inset 0 0 0 1px var(--gray-a5)",
|
"boxShadow": "inset 0 0 0 1px var(--gray-a5)",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ def on_value_event_spec(
|
|||||||
Returns:
|
Returns:
|
||||||
The event handler spec.
|
The event handler spec.
|
||||||
"""
|
"""
|
||||||
return (value,) # type: ignore
|
return (value,)
|
||||||
|
|
||||||
|
|
||||||
class SliderRoot(SliderComponent):
|
class SliderRoot(SliderComponent):
|
||||||
|
@ -17,7 +17,7 @@ rx.text(
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Dict, List, Literal, Optional, Union, get_args
|
from typing import Any, Dict, List, Literal, Optional, Union, get_args
|
||||||
|
|
||||||
from reflex.components.component import BaseComponent
|
from reflex.components.component import BaseComponent
|
||||||
from reflex.components.core.cond import Cond, color_mode_cond, cond
|
from reflex.components.core.cond import Cond, color_mode_cond, cond
|
||||||
@ -78,17 +78,19 @@ position_map: Dict[str, List[str]] = {
|
|||||||
|
|
||||||
|
|
||||||
# needed to inverse contains for find
|
# needed to inverse contains for find
|
||||||
def _find(const: List[str], var):
|
def _find(const: List[str], var: Any):
|
||||||
return LiteralArrayVar.create(const).contains(var)
|
return LiteralArrayVar.create(const).contains(var)
|
||||||
|
|
||||||
|
|
||||||
def _set_var_default(props, position, prop, default1, default2=""):
|
def _set_var_default(
|
||||||
|
props: dict, position: Any, prop: str, default1: str, default2: str = ""
|
||||||
|
):
|
||||||
props.setdefault(
|
props.setdefault(
|
||||||
prop, cond(_find(position_map[prop], position), default1, default2)
|
prop, cond(_find(position_map[prop], position), default1, default2)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _set_static_default(props, position, prop, default):
|
def _set_static_default(props: dict, position: Any, prop: str, default: str):
|
||||||
if prop in position:
|
if prop in position:
|
||||||
props.setdefault(prop, default)
|
props.setdefault(prop, default)
|
||||||
|
|
||||||
@ -115,12 +117,12 @@ class ColorModeIconButton(IconButton):
|
|||||||
Returns:
|
Returns:
|
||||||
The button component.
|
The button component.
|
||||||
"""
|
"""
|
||||||
position = props.pop("position", None)
|
position: str | Var = props.pop("position", None)
|
||||||
allow_system = props.pop("allow_system", False)
|
allow_system = props.pop("allow_system", False)
|
||||||
|
|
||||||
# position is used to set nice defaults for positioning the icon button
|
# position is used to set nice defaults for positioning the icon button
|
||||||
if isinstance(position, Var):
|
if isinstance(position, Var):
|
||||||
_set_var_default(props, position, "position", "fixed", position) # type: ignore
|
_set_var_default(props, position, "position", "fixed", position) # pyright: ignore [reportArgumentType]
|
||||||
_set_var_default(props, position, "bottom", "2rem")
|
_set_var_default(props, position, "bottom", "2rem")
|
||||||
_set_var_default(props, position, "top", "2rem")
|
_set_var_default(props, position, "top", "2rem")
|
||||||
_set_var_default(props, position, "left", "2rem")
|
_set_var_default(props, position, "left", "2rem")
|
||||||
@ -142,7 +144,7 @@ class ColorModeIconButton(IconButton):
|
|||||||
|
|
||||||
if allow_system:
|
if allow_system:
|
||||||
|
|
||||||
def color_mode_item(_color_mode):
|
def color_mode_item(_color_mode: str):
|
||||||
return dropdown_menu.item(
|
return dropdown_menu.item(
|
||||||
_color_mode.title(), on_click=set_color_mode(_color_mode)
|
_color_mode.title(), on_click=set_color_mode(_color_mode)
|
||||||
)
|
)
|
||||||
|
@ -22,6 +22,8 @@ from ..base import (
|
|||||||
|
|
||||||
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
||||||
|
|
||||||
|
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
||||||
|
|
||||||
|
|
||||||
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
||||||
"""A button designed specifically for usage with a single icon."""
|
"""A button designed specifically for usage with a single icon."""
|
||||||
@ -72,8 +74,6 @@ class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
|||||||
"IconButton requires a child icon. Pass a string as the first child or a rx.icon."
|
"IconButton requires a child icon. Pass a string as the first child or a rx.icon."
|
||||||
)
|
)
|
||||||
if "size" in props:
|
if "size" in props:
|
||||||
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
|
||||||
|
|
||||||
if isinstance(props["size"], str):
|
if isinstance(props["size"], str):
|
||||||
children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
|
children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
|
||||||
else:
|
else:
|
||||||
|
@ -14,6 +14,7 @@ from reflex.vars.base import Var
|
|||||||
from ..base import RadixLoadingProp, RadixThemesComponent
|
from ..base import RadixLoadingProp, RadixThemesComponent
|
||||||
|
|
||||||
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
LiteralButtonSize = Literal["1", "2", "3", "4"]
|
||||||
|
RADIX_TO_LUCIDE_SIZE = {"1": 12, "2": 24, "3": 36, "4": 48}
|
||||||
|
|
||||||
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
class IconButton(elements.Button, RadixLoadingProp, RadixThemesComponent):
|
||||||
@overload
|
@overload
|
||||||
|
@ -85,6 +85,8 @@ class RadioCardsItem(RadixThemesComponent):
|
|||||||
# When true, indicates that the user must check the radio item before the owning form can be submitted.
|
# When true, indicates that the user must check the radio item before the owning form can be submitted.
|
||||||
required: Var[bool]
|
required: Var[bool]
|
||||||
|
|
||||||
|
_valid_parents: list[str] = ["RadioCardsRoot"]
|
||||||
|
|
||||||
|
|
||||||
class RadioCards(SimpleNamespace):
|
class RadioCards(SimpleNamespace):
|
||||||
"""RadioCards components namespace."""
|
"""RadioCards components namespace."""
|
||||||
|
@ -155,7 +155,7 @@ class HighLevelRadioGroup(RadixThemesComponent):
|
|||||||
if isinstance(default_value, str) or (
|
if isinstance(default_value, str) or (
|
||||||
isinstance(default_value, Var) and default_value._var_type is str
|
isinstance(default_value, Var) and default_value._var_type is str
|
||||||
):
|
):
|
||||||
default_value = LiteralVar.create(default_value) # type: ignore
|
default_value = LiteralVar.create(default_value)
|
||||||
else:
|
else:
|
||||||
default_value = LiteralVar.create(default_value).to_string()
|
default_value = LiteralVar.create(default_value).to_string()
|
||||||
|
|
||||||
|
@ -96,5 +96,17 @@ class TextArea(RadixThemesComponent, elements.Textarea):
|
|||||||
return DebounceInput.create(super().create(*children, **props))
|
return DebounceInput.create(super().create(*children, **props))
|
||||||
return super().create(*children, **props)
|
return super().create(*children, **props)
|
||||||
|
|
||||||
|
def add_style(self):
|
||||||
|
"""Add the style to the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The style of the component.
|
||||||
|
"""
|
||||||
|
added_style: dict[str, dict] = {}
|
||||||
|
added_style.setdefault("& textarea", {})
|
||||||
|
if "padding" in self.style:
|
||||||
|
added_style["& textarea"]["padding"] = self.style.pop("padding")
|
||||||
|
return added_style
|
||||||
|
|
||||||
|
|
||||||
text_area = TextArea.create
|
text_area = TextArea.create
|
||||||
|
@ -268,4 +268,6 @@ class TextArea(RadixThemesComponent, elements.Textarea):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def add_style(self): ...
|
||||||
|
|
||||||
text_area = TextArea.create
|
text_area = TextArea.create
|
||||||
|
@ -105,7 +105,7 @@ class TextFieldRoot(elements.Input, RadixThemesComponent):
|
|||||||
(value_var := Var.create(value))._var_type
|
(value_var := Var.create(value))._var_type
|
||||||
):
|
):
|
||||||
props["value"] = ternary_operation(
|
props["value"] = ternary_operation(
|
||||||
(value_var != Var.create(None)) # pyright: ignore [reportGeneralTypeIssues]
|
(value_var != Var.create(None)) # pyright: ignore [reportArgumentType]
|
||||||
& (value_var != Var(_js_expr="undefined")),
|
& (value_var != Var(_js_expr="undefined")),
|
||||||
value,
|
value,
|
||||||
Var.create(""),
|
Var.create(""),
|
||||||
|
@ -28,6 +28,9 @@ LiteralStickyType = Literal[
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
ARIA_LABEL_KEY = "aria_label"
|
||||||
|
|
||||||
|
|
||||||
# The Tooltip inherits props from the Tooltip.Root, Tooltip.Portal, Tooltip.Content
|
# The Tooltip inherits props from the Tooltip.Root, Tooltip.Portal, Tooltip.Content
|
||||||
class Tooltip(RadixThemesComponent):
|
class Tooltip(RadixThemesComponent):
|
||||||
"""Floating element that provides a control with contextual information via pointer or focus."""
|
"""Floating element that provides a control with contextual information via pointer or focus."""
|
||||||
@ -104,7 +107,6 @@ class Tooltip(RadixThemesComponent):
|
|||||||
Returns:
|
Returns:
|
||||||
The created component.
|
The created component.
|
||||||
"""
|
"""
|
||||||
ARIA_LABEL_KEY = "aria_label"
|
|
||||||
if props.get(ARIA_LABEL_KEY) is not None:
|
if props.get(ARIA_LABEL_KEY) is not None:
|
||||||
props[format.to_kebab_case(ARIA_LABEL_KEY)] = props.pop(ARIA_LABEL_KEY)
|
props[format.to_kebab_case(ARIA_LABEL_KEY)] = props.pop(ARIA_LABEL_KEY)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from ..base import RadixThemesComponent
|
|||||||
LiteralSideType = Literal["top", "right", "bottom", "left"]
|
LiteralSideType = Literal["top", "right", "bottom", "left"]
|
||||||
LiteralAlignType = Literal["start", "center", "end"]
|
LiteralAlignType = Literal["start", "center", "end"]
|
||||||
LiteralStickyType = Literal["partial", "always"]
|
LiteralStickyType = Literal["partial", "always"]
|
||||||
|
ARIA_LABEL_KEY = "aria_label"
|
||||||
|
|
||||||
class Tooltip(RadixThemesComponent):
|
class Tooltip(RadixThemesComponent):
|
||||||
@overload
|
@overload
|
||||||
|
@ -9,7 +9,7 @@ from .container import container as container
|
|||||||
from .flex import flex as flex
|
from .flex import flex as flex
|
||||||
from .grid import grid as grid
|
from .grid import grid as grid
|
||||||
from .list import list_item as list_item
|
from .list import list_item as list_item
|
||||||
from .list import list_ns as list # noqa
|
from .list import list_ns as list # noqa: F401
|
||||||
from .list import ordered_list as ordered_list
|
from .list import ordered_list as ordered_list
|
||||||
from .list import unordered_list as unordered_list
|
from .list import unordered_list as unordered_list
|
||||||
from .section import section as section
|
from .section import section as section
|
||||||
|
@ -72,7 +72,7 @@ class BaseList(Component, MarkdownComponentMap):
|
|||||||
if isinstance(items, Var):
|
if isinstance(items, Var):
|
||||||
children = [Foreach.create(items, ListItem.create)]
|
children = [Foreach.create(items, ListItem.create)]
|
||||||
else:
|
else:
|
||||||
children = [ListItem.create(item) for item in items] # type: ignore
|
children = [ListItem.create(item) for item in items]
|
||||||
props["direction"] = "column"
|
props["direction"] = "column"
|
||||||
style = props.setdefault("style", {})
|
style = props.setdefault("style", {})
|
||||||
style["list_style_type"] = list_style_type
|
style["list_style_type"] = list_style_type
|
||||||
@ -189,7 +189,7 @@ ordered_list = list_ns.ordered
|
|||||||
unordered_list = list_ns.unordered
|
unordered_list = list_ns.unordered
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name: Any):
|
||||||
# special case for when accessing list to avoid shadowing
|
# special case for when accessing list to avoid shadowing
|
||||||
# python's built in list object.
|
# python's built in list object.
|
||||||
if name == "list":
|
if name == "list":
|
||||||
|
@ -49,14 +49,14 @@ class VStack(Stack):
|
|||||||
"""A vertical stack component."""
|
"""A vertical stack component."""
|
||||||
|
|
||||||
# The direction of the stack.
|
# The direction of the stack.
|
||||||
direction: Var[LiteralFlexDirection] = "column" # type: ignore
|
direction: Var[LiteralFlexDirection] = Var.create("column")
|
||||||
|
|
||||||
|
|
||||||
class HStack(Stack):
|
class HStack(Stack):
|
||||||
"""A horizontal stack component."""
|
"""A horizontal stack component."""
|
||||||
|
|
||||||
# The direction of the stack.
|
# The direction of the stack.
|
||||||
direction: Var[LiteralFlexDirection] = "row" # type: ignore
|
direction: Var[LiteralFlexDirection] = Var.create("row")
|
||||||
|
|
||||||
|
|
||||||
stack = Stack.create
|
stack = Stack.create
|
||||||
|
@ -60,7 +60,7 @@ class Link(RadixThemesComponent, A, MemoizationLeaf, MarkdownComponentMap):
|
|||||||
Returns:
|
Returns:
|
||||||
The import dict.
|
The import dict.
|
||||||
"""
|
"""
|
||||||
return next_link._get_imports() # type: ignore
|
return next_link._get_imports() # pyright: ignore [reportReturnType]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children, **props) -> Component:
|
def create(cls, *children, **props) -> Component:
|
||||||
|
@ -47,7 +47,7 @@ class Text(elements.Span, RadixThemesComponent, MarkdownComponentMap):
|
|||||||
as_child: Var[bool]
|
as_child: Var[bool]
|
||||||
|
|
||||||
# Change the default rendered element into a semantically appropriate alternative (cannot be used with asChild)
|
# Change the default rendered element into a semantically appropriate alternative (cannot be used with asChild)
|
||||||
as_: Var[LiteralType] = "p" # type: ignore
|
as_: Var[LiteralType] = Var.create("p")
|
||||||
|
|
||||||
# Text size: "1" - "9"
|
# Text size: "1" - "9"
|
||||||
size: Var[Responsive[LiteralTextSize]]
|
size: Var[Responsive[LiteralTextSize]]
|
||||||
@ -71,7 +71,7 @@ class Text(elements.Span, RadixThemesComponent, MarkdownComponentMap):
|
|||||||
class Span(Text):
|
class Span(Text):
|
||||||
"""A variant of text rendering as <span> element."""
|
"""A variant of text rendering as <span> element."""
|
||||||
|
|
||||||
as_: Var[LiteralType] = "span" # type: ignore
|
as_: Var[LiteralType] = Var.create("span")
|
||||||
|
|
||||||
|
|
||||||
class Em(elements.Em, RadixThemesComponent):
|
class Em(elements.Em, RadixThemesComponent):
|
||||||
|
@ -39,7 +39,7 @@ class ReactPlayer(NoSSRComponent):
|
|||||||
loop: Var[bool]
|
loop: Var[bool]
|
||||||
|
|
||||||
# Set to true or false to display native player controls.
|
# Set to true or false to display native player controls.
|
||||||
controls: Var[bool] = True # type: ignore
|
controls: Var[bool] = Var.create(True)
|
||||||
|
|
||||||
# Set to true to show just the video thumbnail, which loads the full player on click
|
# Set to true to show just the video thumbnail, which loads the full player on click
|
||||||
light: Var[bool]
|
light: Var[bool]
|
||||||
|
@ -70,6 +70,8 @@ _SUBMOD_ATTRS: dict = {
|
|||||||
"Label",
|
"Label",
|
||||||
"label_list",
|
"label_list",
|
||||||
"LabelList",
|
"LabelList",
|
||||||
|
"cell",
|
||||||
|
"Cell",
|
||||||
],
|
],
|
||||||
"polar": [
|
"polar": [
|
||||||
"pie",
|
"pie",
|
||||||
|
@ -53,11 +53,13 @@ from .charts import radar_chart as radar_chart
|
|||||||
from .charts import radial_bar_chart as radial_bar_chart
|
from .charts import radial_bar_chart as radial_bar_chart
|
||||||
from .charts import scatter_chart as scatter_chart
|
from .charts import scatter_chart as scatter_chart
|
||||||
from .charts import treemap as treemap
|
from .charts import treemap as treemap
|
||||||
|
from .general import Cell as Cell
|
||||||
from .general import GraphingTooltip as GraphingTooltip
|
from .general import GraphingTooltip as GraphingTooltip
|
||||||
from .general import Label as Label
|
from .general import Label as Label
|
||||||
from .general import LabelList as LabelList
|
from .general import LabelList as LabelList
|
||||||
from .general import Legend as Legend
|
from .general import Legend as Legend
|
||||||
from .general import ResponsiveContainer as ResponsiveContainer
|
from .general import ResponsiveContainer as ResponsiveContainer
|
||||||
|
from .general import cell as cell
|
||||||
from .general import graphing_tooltip as graphing_tooltip
|
from .general import graphing_tooltip as graphing_tooltip
|
||||||
from .general import label as label
|
from .general import label as label
|
||||||
from .general import label_list as label_list
|
from .general import label_list as label_list
|
||||||
|
@ -25,10 +25,10 @@ class ChartBase(RechartsCharts):
|
|||||||
"""A component that wraps a Recharts charts."""
|
"""A component that wraps a Recharts charts."""
|
||||||
|
|
||||||
# The width of chart container. String or Integer
|
# The width of chart container. String or Integer
|
||||||
width: Var[Union[str, int]] = "100%" # type: ignore
|
width: Var[Union[str, int]] = Var.create("100%")
|
||||||
|
|
||||||
# The height of chart container.
|
# The height of chart container.
|
||||||
height: Var[Union[str, int]] = "100%" # type: ignore
|
height: Var[Union[str, int]] = Var.create("100%")
|
||||||
|
|
||||||
# The customized event handler of click on the component in this chart
|
# The customized event handler of click on the component in this chart
|
||||||
on_click: EventHandler[no_args_event_spec]
|
on_click: EventHandler[no_args_event_spec]
|
||||||
@ -69,7 +69,7 @@ class ChartBase(RechartsCharts):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, *children, **props) -> Component:
|
def create(cls, *children: Any, **props: Any) -> Component:
|
||||||
"""Create a chart component.
|
"""Create a chart component.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -84,19 +84,19 @@ class ChartBase(RechartsCharts):
|
|||||||
cls._ensure_valid_dimension("width", width)
|
cls._ensure_valid_dimension("width", width)
|
||||||
cls._ensure_valid_dimension("height", height)
|
cls._ensure_valid_dimension("height", height)
|
||||||
|
|
||||||
dim_props = {
|
# Ensure that the min_height and min_width are set to prevent the chart from collapsing.
|
||||||
"width": width if width is not None else "100%",
|
# We are using small values so that height and width can still be used over min_height and min_width.
|
||||||
"height": height if height is not None else "100%",
|
# Without this, sometimes the chart will not be visible. Causing confusion to the user.
|
||||||
}
|
# With this, the user will see a small chart and can adjust the height and width and can figure out that the issue is with the size.
|
||||||
# Provide min dimensions so the graph always appears, even if the outer container is zero-size.
|
min_height = props.pop("min_height", 10)
|
||||||
if width is None:
|
min_width = props.pop("min_width", 10)
|
||||||
dim_props["min_width"] = 200
|
|
||||||
if height is None:
|
|
||||||
dim_props["min_height"] = 100
|
|
||||||
|
|
||||||
return ResponsiveContainer.create(
|
return ResponsiveContainer.create(
|
||||||
super().create(*children, **props),
|
super().create(*children, **props),
|
||||||
**dim_props, # type: ignore
|
width=width if width is not None else "100%",
|
||||||
|
height=height if height is not None else "100%",
|
||||||
|
min_width=min_width,
|
||||||
|
min_height=min_height,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -458,10 +458,10 @@ class Treemap(RechartsCharts):
|
|||||||
alias = "RechartsTreemap"
|
alias = "RechartsTreemap"
|
||||||
|
|
||||||
# The width of chart container. String or Integer. Default: "100%"
|
# The width of chart container. String or Integer. Default: "100%"
|
||||||
width: Var[Union[str, int]] = "100%" # type: ignore
|
width: Var[Union[str, int]] = Var.create("100%")
|
||||||
|
|
||||||
# The height of chart container. String or Integer. Default: "100%"
|
# The height of chart container. String or Integer. Default: "100%"
|
||||||
height: Var[Union[str, int]] = "100%" # type: ignore
|
height: Var[Union[str, int]] = Var.create("100%")
|
||||||
|
|
||||||
# data of treemap. Array
|
# data of treemap. Array
|
||||||
data: Var[List[Dict[str, Any]]]
|
data: Var[List[Dict[str, Any]]]
|
||||||
|
@ -36,11 +36,11 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
|
|||||||
# The height of chart container. Can be a number or string. Default: "100%"
|
# The height of chart container. Can be a number or string. Default: "100%"
|
||||||
height: Var[Union[int, str]]
|
height: Var[Union[int, str]]
|
||||||
|
|
||||||
# The minimum width of chart container. Number
|
# The minimum width of chart container. Number or string.
|
||||||
min_width: Var[int]
|
min_width: Var[Union[int, str]]
|
||||||
|
|
||||||
# The minimum height of chart container. Number
|
# The minimum height of chart container. Number or string.
|
||||||
min_height: Var[int]
|
min_height: Var[Union[int, str]]
|
||||||
|
|
||||||
# If specified a positive number, debounced function will be used to handle the resize event. Default: 0
|
# If specified a positive number, debounced function will be used to handle the resize event. Default: 0
|
||||||
debounce: Var[int]
|
debounce: Var[int]
|
||||||
@ -242,8 +242,23 @@ class LabelList(Recharts):
|
|||||||
stroke: Var[Union[str, Color]] = LiteralVar.create("none")
|
stroke: Var[Union[str, Color]] = LiteralVar.create("none")
|
||||||
|
|
||||||
|
|
||||||
|
class Cell(Recharts):
|
||||||
|
"""A Cell component in Recharts."""
|
||||||
|
|
||||||
|
tag = "Cell"
|
||||||
|
|
||||||
|
alias = "RechartsCell"
|
||||||
|
|
||||||
|
# The presentation attribute of a rectangle in bar or a sector in pie.
|
||||||
|
fill: Var[str | Color]
|
||||||
|
|
||||||
|
# The presentation attribute of a rectangle in bar or a sector in pie.
|
||||||
|
stroke: Var[str | Color]
|
||||||
|
|
||||||
|
|
||||||
responsive_container = ResponsiveContainer.create
|
responsive_container = ResponsiveContainer.create
|
||||||
legend = Legend.create
|
legend = Legend.create
|
||||||
graphing_tooltip = GraphingTooltip.create
|
graphing_tooltip = GraphingTooltip.create
|
||||||
label = Label.create
|
label = Label.create
|
||||||
label_list = LabelList.create
|
label_list = LabelList.create
|
||||||
|
cell = Cell.create
|
||||||
|
@ -22,8 +22,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
|
|||||||
aspect: Optional[Union[Var[int], int]] = None,
|
aspect: Optional[Union[Var[int], int]] = None,
|
||||||
width: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
width: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||||
height: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
height: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||||
min_width: Optional[Union[Var[int], int]] = None,
|
min_width: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||||
min_height: Optional[Union[Var[int], int]] = None,
|
min_height: Optional[Union[Var[Union[int, str]], int, str]] = None,
|
||||||
debounce: Optional[Union[Var[int], int]] = None,
|
debounce: Optional[Union[Var[int], int]] = None,
|
||||||
style: Optional[Style] = None,
|
style: Optional[Style] = None,
|
||||||
key: Optional[Any] = None,
|
key: Optional[Any] = None,
|
||||||
@ -56,8 +56,8 @@ class ResponsiveContainer(Recharts, MemoizationLeaf):
|
|||||||
aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
|
aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
|
||||||
width: The width of chart container. Can be a number or string. Default: "100%"
|
width: The width of chart container. Can be a number or string. Default: "100%"
|
||||||
height: The height of chart container. Can be a number or string. Default: "100%"
|
height: The height of chart container. Can be a number or string. Default: "100%"
|
||||||
min_width: The minimum width of chart container. Number
|
min_width: The minimum width of chart container. Number or string.
|
||||||
min_height: The minimum height of chart container. Number
|
min_height: The minimum height of chart container. Number or string.
|
||||||
debounce: If specified a positive number, debounced function will be used to handle the resize event. Default: 0
|
debounce: If specified a positive number, debounced function will be used to handle the resize event. Default: 0
|
||||||
on_resize: If specified provides a callback providing the updated chart width and height values.
|
on_resize: If specified provides a callback providing the updated chart width and height values.
|
||||||
style: The style of the component.
|
style: The style of the component.
|
||||||
@ -482,8 +482,59 @@ class LabelList(Recharts):
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
class Cell(Recharts):
|
||||||
|
@overload
|
||||||
|
@classmethod
|
||||||
|
def create( # type: ignore
|
||||||
|
cls,
|
||||||
|
*children,
|
||||||
|
fill: Optional[Union[Color, Var[Union[Color, str]], str]] = None,
|
||||||
|
stroke: Optional[Union[Color, Var[Union[Color, str]], str]] = 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, Any]]] = None,
|
||||||
|
on_blur: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_click: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_context_menu: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_double_click: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_focus: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mount: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_down: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_move: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_out: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_over: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_mouse_up: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_scroll: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
on_unmount: Optional[EventType[[], BASE_STATE]] = None,
|
||||||
|
**props,
|
||||||
|
) -> "Cell":
|
||||||
|
"""Create the component.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*children: The children of the component.
|
||||||
|
fill: The presentation attribute of a rectangle in bar or a sector in pie.
|
||||||
|
stroke: The presentation attribute of a rectangle in bar or a sector in pie.
|
||||||
|
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 of the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The component.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
responsive_container = ResponsiveContainer.create
|
responsive_container = ResponsiveContainer.create
|
||||||
legend = Legend.create
|
legend = Legend.create
|
||||||
graphing_tooltip = GraphingTooltip.create
|
graphing_tooltip = GraphingTooltip.create
|
||||||
label = Label.create
|
label = Label.create
|
||||||
label_list = LabelList.create
|
label_list = LabelList.create
|
||||||
|
cell = Cell.create
|
||||||
|
@ -64,7 +64,7 @@ class Pie(Recharts):
|
|||||||
legend_type: Var[LiteralLegendType]
|
legend_type: Var[LiteralLegendType]
|
||||||
|
|
||||||
# If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False
|
# If false set, labels will not be drawn. If true set, labels will be drawn which have the props calculated internally. Default: False
|
||||||
label: Var[bool] = False # type: ignore
|
label: Var[bool] = Var.create(False)
|
||||||
|
|
||||||
# If false set, label lines will not be drawn. If true set, label lines will be drawn which have the props calculated internally. Default: False
|
# If false set, label lines will not be drawn. If true set, label lines will be drawn which have the props calculated internally. Default: False
|
||||||
label_line: Var[bool]
|
label_line: Var[bool]
|
||||||
@ -73,7 +73,7 @@ class Pie(Recharts):
|
|||||||
data: Var[List[Dict[str, Any]]]
|
data: Var[List[Dict[str, Any]]]
|
||||||
|
|
||||||
# Valid children components
|
# Valid children components
|
||||||
_valid_children: List[str] = ["Cell", "LabelList"]
|
_valid_children: List[str] = ["Cell", "LabelList", "Bare"]
|
||||||
|
|
||||||
# Stoke color. Default: rx.color("accent", 9)
|
# Stoke color. Default: rx.color("accent", 9)
|
||||||
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
|
stroke: Var[Union[str, Color]] = LiteralVar.create(Color("accent", 9))
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""A component that wraps a recharts lib."""
|
"""A component that wraps a recharts lib."""
|
||||||
|
|
||||||
from typing import Dict, Literal
|
from typing import Literal
|
||||||
|
|
||||||
from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent
|
from reflex.components.component import Component, MemoizationLeaf, NoSSRComponent
|
||||||
|
|
||||||
@ -8,16 +8,16 @@ from reflex.components.component import Component, MemoizationLeaf, NoSSRCompone
|
|||||||
class Recharts(Component):
|
class Recharts(Component):
|
||||||
"""A component that wraps a recharts lib."""
|
"""A component that wraps a recharts lib."""
|
||||||
|
|
||||||
library = "recharts@2.13.0"
|
library = "recharts@2.15.0"
|
||||||
|
|
||||||
def _get_style(self) -> Dict:
|
def _get_style(self) -> dict:
|
||||||
return {"wrapperStyle": self.style}
|
return {"wrapperStyle": self.style}
|
||||||
|
|
||||||
|
|
||||||
class RechartsCharts(NoSSRComponent, MemoizationLeaf):
|
class RechartsCharts(NoSSRComponent, MemoizationLeaf):
|
||||||
"""A component that wraps a recharts lib."""
|
"""A component that wraps a recharts lib."""
|
||||||
|
|
||||||
library = "recharts@2.13.0"
|
library = "recharts@2.15.0"
|
||||||
|
|
||||||
|
|
||||||
LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"]
|
LiteralAnimationEasing = Literal["ease", "ease-in", "ease-out", "ease-in-out", "linear"]
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user