reflex 0.7.14a6__py3-none-any.whl → 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reflex might be problematic. Click here for more details.
- reflex/.templates/jinja/app/rxconfig.py.jinja2 +4 -1
- reflex/.templates/jinja/web/package.json.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/_app.js.jinja2 +21 -11
- reflex/.templates/jinja/web/pages/_document.js.jinja2 +1 -1
- reflex/.templates/jinja/web/pages/base_page.js.jinja2 +0 -1
- reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +4 -0
- reflex/.templates/jinja/web/styles/styles.css.jinja2 +1 -0
- reflex/.templates/jinja/web/utils/context.js.jinja2 +25 -8
- reflex/.templates/web/app/entry.client.js +8 -0
- reflex/.templates/web/app/routes.js +10 -0
- reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +12 -37
- reflex/.templates/web/postcss.config.js +1 -1
- reflex/.templates/web/react-router.config.js +6 -0
- reflex/.templates/web/styles/__reflex_style_reset.css +399 -0
- reflex/.templates/web/utils/client_side_routing.js +21 -19
- reflex/.templates/web/utils/react-theme.js +92 -0
- reflex/.templates/web/utils/state.js +251 -100
- reflex/.templates/web/vite-plugin-safari-cachebust.js +160 -0
- reflex/.templates/web/vite.config.js +39 -0
- reflex/__init__.py +1 -6
- reflex/__init__.pyi +327 -192
- reflex/app.py +86 -135
- reflex/base.py +1 -87
- reflex/compiler/compiler.py +70 -19
- reflex/compiler/templates.py +3 -3
- reflex/compiler/utils.py +91 -33
- reflex/components/__init__.py +0 -2
- reflex/components/__init__.pyi +34 -18
- reflex/components/base/__init__.py +1 -5
- reflex/components/base/__init__.pyi +30 -21
- reflex/components/base/app_wrap.pyi +7 -7
- reflex/components/base/body.pyi +7 -7
- reflex/components/base/document.py +18 -14
- reflex/components/base/document.pyi +88 -38
- reflex/components/base/error_boundary.pyi +7 -7
- reflex/components/base/fragment.pyi +7 -7
- reflex/components/base/link.pyi +12 -12
- reflex/components/base/meta.py +4 -15
- reflex/components/base/meta.pyi +31 -31
- reflex/components/base/script.py +60 -58
- reflex/components/base/script.pyi +248 -34
- reflex/components/base/strict_mode.pyi +7 -7
- reflex/components/component.py +146 -217
- reflex/components/core/__init__.py +1 -0
- reflex/components/core/__init__.pyi +77 -37
- reflex/components/core/auto_scroll.pyi +7 -7
- reflex/components/core/banner.pyi +33 -33
- reflex/components/core/client_side_routing.py +7 -6
- reflex/components/core/client_side_routing.pyi +8 -59
- reflex/components/core/clipboard.pyi +7 -7
- reflex/components/core/debounce.py +1 -0
- reflex/components/core/debounce.pyi +7 -7
- reflex/components/core/foreach.py +5 -4
- reflex/components/core/helmet.py +14 -0
- reflex/components/{next/base.pyi → core/helmet.pyi} +12 -10
- reflex/components/core/html.pyi +7 -7
- reflex/components/core/match.py +3 -3
- reflex/components/core/sticky.pyi +21 -20
- reflex/components/core/upload.py +4 -2
- reflex/components/core/upload.pyi +26 -25
- reflex/components/datadisplay/__init__.pyi +13 -7
- reflex/components/datadisplay/code.py +14 -79
- reflex/components/datadisplay/code.pyi +11 -13
- reflex/components/datadisplay/dataeditor.pyi +38 -15
- reflex/components/datadisplay/shiki_code_block.py +5 -3
- reflex/components/datadisplay/shiki_code_block.pyi +16 -15
- reflex/components/dynamic.py +5 -5
- reflex/components/el/__init__.pyi +506 -246
- reflex/components/el/element.pyi +7 -7
- reflex/components/el/elements/__init__.pyi +504 -245
- reflex/components/el/elements/base.pyi +7 -7
- reflex/components/el/elements/forms.pyi +146 -101
- reflex/components/el/elements/inline.pyi +142 -142
- reflex/components/el/elements/media.pyi +131 -130
- reflex/components/el/elements/metadata.pyi +32 -32
- reflex/components/el/elements/other.pyi +37 -37
- reflex/components/el/elements/scripts.pyi +17 -17
- reflex/components/el/elements/sectioning.pyi +77 -77
- reflex/components/el/elements/tables.pyi +52 -52
- reflex/components/el/elements/typography.pyi +77 -77
- reflex/components/field.py +175 -0
- reflex/components/gridjs/datatable.py +2 -2
- reflex/components/gridjs/datatable.pyi +14 -14
- reflex/components/lucide/icon.py +6 -2
- reflex/components/lucide/icon.pyi +19 -17
- reflex/components/markdown/markdown.py +5 -3
- reflex/components/markdown/markdown.pyi +7 -7
- reflex/components/moment/moment.py +1 -1
- reflex/components/moment/moment.pyi +7 -7
- reflex/components/plotly/plotly.py +12 -6
- reflex/components/plotly/plotly.pyi +50 -49
- reflex/components/props.py +376 -27
- reflex/components/radix/__init__.pyi +123 -65
- reflex/components/radix/primitives/__init__.pyi +6 -4
- reflex/components/radix/primitives/accordion.py +8 -1
- reflex/components/radix/primitives/accordion.pyi +37 -37
- reflex/components/radix/primitives/base.pyi +12 -12
- reflex/components/radix/primitives/drawer.pyi +56 -55
- reflex/components/radix/primitives/form.pyi +63 -53
- reflex/components/radix/primitives/progress.pyi +26 -25
- reflex/components/radix/primitives/slider.pyi +27 -27
- reflex/components/radix/themes/__init__.pyi +5 -6
- reflex/components/radix/themes/base.py +3 -3
- reflex/components/radix/themes/base.pyi +42 -42
- reflex/components/radix/themes/color_mode.py +5 -6
- reflex/components/radix/themes/color_mode.pyi +17 -17
- reflex/components/radix/themes/components/__init__.pyi +75 -38
- reflex/components/radix/themes/components/alert_dialog.pyi +37 -37
- reflex/components/radix/themes/components/aspect_ratio.pyi +7 -7
- reflex/components/radix/themes/components/avatar.pyi +7 -7
- reflex/components/radix/themes/components/badge.pyi +7 -7
- reflex/components/radix/themes/components/button.pyi +7 -7
- reflex/components/radix/themes/components/callout.pyi +26 -25
- reflex/components/radix/themes/components/card.pyi +7 -7
- reflex/components/radix/themes/components/checkbox.pyi +16 -15
- reflex/components/radix/themes/components/checkbox_cards.pyi +12 -12
- reflex/components/radix/themes/components/checkbox_group.pyi +12 -12
- reflex/components/radix/themes/components/context_menu.pyi +67 -67
- reflex/components/radix/themes/components/data_list.pyi +22 -22
- reflex/components/radix/themes/components/dialog.pyi +36 -35
- reflex/components/radix/themes/components/dropdown_menu.pyi +42 -42
- reflex/components/radix/themes/components/hover_card.pyi +21 -20
- reflex/components/radix/themes/components/icon_button.pyi +7 -7
- reflex/components/radix/themes/components/inset.pyi +7 -7
- reflex/components/radix/themes/components/popover.pyi +22 -22
- reflex/components/radix/themes/components/progress.pyi +7 -7
- reflex/components/radix/themes/components/radio.pyi +7 -7
- reflex/components/radix/themes/components/radio_cards.pyi +12 -12
- reflex/components/radix/themes/components/radio_group.pyi +21 -20
- reflex/components/radix/themes/components/scroll_area.pyi +7 -7
- reflex/components/radix/themes/components/segmented_control.pyi +12 -12
- reflex/components/radix/themes/components/select.pyi +46 -45
- reflex/components/radix/themes/components/separator.pyi +7 -7
- reflex/components/radix/themes/components/skeleton.pyi +7 -7
- reflex/components/radix/themes/components/slider.pyi +17 -9
- reflex/components/radix/themes/components/spinner.pyi +7 -7
- reflex/components/radix/themes/components/switch.pyi +7 -7
- reflex/components/radix/themes/components/table.pyi +37 -37
- reflex/components/radix/themes/components/tabs.pyi +26 -25
- reflex/components/radix/themes/components/text_area.pyi +15 -9
- reflex/components/radix/themes/components/text_field.pyi +32 -19
- reflex/components/radix/themes/components/tooltip.pyi +7 -7
- reflex/components/radix/themes/layout/__init__.pyi +27 -14
- reflex/components/radix/themes/layout/base.pyi +7 -7
- reflex/components/radix/themes/layout/box.pyi +7 -7
- reflex/components/radix/themes/layout/center.pyi +7 -7
- reflex/components/radix/themes/layout/container.pyi +7 -7
- reflex/components/radix/themes/layout/flex.pyi +7 -7
- reflex/components/radix/themes/layout/grid.pyi +7 -7
- reflex/components/radix/themes/layout/list.pyi +26 -25
- reflex/components/radix/themes/layout/section.pyi +7 -7
- reflex/components/radix/themes/layout/spacer.pyi +7 -7
- reflex/components/radix/themes/layout/stack.pyi +17 -17
- reflex/components/radix/themes/typography/__init__.pyi +7 -5
- reflex/components/radix/themes/typography/blockquote.pyi +7 -7
- reflex/components/radix/themes/typography/code.pyi +7 -7
- reflex/components/radix/themes/typography/heading.pyi +7 -7
- reflex/components/radix/themes/typography/link.py +46 -11
- reflex/components/radix/themes/typography/link.pyi +312 -9
- reflex/components/radix/themes/typography/text.pyi +36 -35
- reflex/components/react_player/audio.pyi +10 -8
- reflex/components/react_player/react_player.pyi +7 -7
- reflex/components/react_player/video.pyi +10 -8
- reflex/components/recharts/__init__.pyi +208 -100
- reflex/components/recharts/cartesian.py +10 -8
- reflex/components/recharts/cartesian.pyi +90 -94
- reflex/components/recharts/charts.py +4 -2
- reflex/components/recharts/charts.pyi +49 -49
- reflex/components/recharts/general.pyi +31 -31
- reflex/components/recharts/polar.py +8 -4
- reflex/components/recharts/polar.pyi +23 -23
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/recharts/recharts.pyi +12 -12
- reflex/components/sonner/toast.py +3 -3
- reflex/components/sonner/toast.pyi +9 -9
- reflex/config.py +10 -113
- reflex/constants/__init__.py +2 -2
- reflex/constants/base.py +28 -11
- reflex/constants/compiler.py +12 -3
- reflex/constants/event.py +1 -0
- reflex/constants/installer.py +26 -20
- reflex/constants/route.py +27 -8
- reflex/constants/state.py +2 -0
- reflex/custom_components/custom_components.py +0 -14
- reflex/environment.py +77 -5
- reflex/event.py +178 -81
- reflex/experimental/__init__.py +0 -30
- reflex/istate/__init__.py +69 -0
- reflex/istate/manager.py +1 -0
- reflex/istate/proxy.py +5 -3
- reflex/page.py +0 -27
- reflex/plugins/__init__.py +3 -2
- reflex/plugins/base.py +5 -1
- reflex/plugins/shared_tailwind.py +215 -0
- reflex/plugins/sitemap.py +206 -0
- reflex/plugins/tailwind_v3.py +15 -108
- reflex/plugins/tailwind_v4.py +18 -110
- reflex/reflex.py +1 -0
- reflex/route.py +157 -75
- reflex/state.py +171 -155
- reflex/testing.py +86 -16
- reflex/utils/build.py +38 -82
- reflex/utils/exec.py +83 -175
- reflex/utils/export.py +2 -2
- reflex/utils/format.py +1 -5
- reflex/utils/imports.py +5 -16
- reflex/utils/misc.py +67 -0
- reflex/utils/prerequisites.py +66 -68
- reflex/utils/processes.py +24 -47
- reflex/utils/pyi_generator.py +44 -49
- reflex/utils/serializers.py +14 -1
- reflex/utils/telemetry.py +0 -15
- reflex/utils/types.py +197 -62
- reflex/vars/__init__.py +2 -0
- reflex/vars/base.py +367 -134
- {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/METADATA +15 -8
- reflex-0.8.0.dist-info/RECORD +403 -0
- reflex/.templates/web/next.config.js +0 -7
- reflex/components/base/head.py +0 -20
- reflex/components/base/head.pyi +0 -116
- reflex/components/next/__init__.py +0 -10
- reflex/components/next/base.py +0 -7
- reflex/components/next/image.py +0 -117
- reflex/components/next/image.pyi +0 -94
- reflex/components/next/link.py +0 -20
- reflex/components/next/link.pyi +0 -67
- reflex/components/next/video.py +0 -38
- reflex/components/next/video.pyi +0 -68
- reflex/components/suneditor/__init__.py +0 -5
- reflex/components/suneditor/editor.py +0 -269
- reflex/components/suneditor/editor.pyi +0 -199
- reflex/experimental/layout.py +0 -254
- reflex/experimental/layout.pyi +0 -814
- reflex-0.7.14a6.dist-info/RECORD +0 -408
- {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/WHEEL +0 -0
- {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.14a6.dist-info → reflex-0.8.0.dist-info}/licenses/LICENSE +0 -0
reflex/plugins/tailwind_v3.py
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"""Base class for all plugins."""
|
|
2
2
|
|
|
3
|
+
import dataclasses
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from types import SimpleNamespace
|
|
5
6
|
|
|
6
7
|
from reflex.constants.base import Dirs
|
|
7
8
|
from reflex.constants.compiler import Ext, PageNames
|
|
8
|
-
from reflex.plugins.
|
|
9
|
-
|
|
9
|
+
from reflex.plugins.shared_tailwind import (
|
|
10
|
+
TailwindConfig,
|
|
11
|
+
TailwindPlugin,
|
|
12
|
+
tailwind_config_js_template,
|
|
13
|
+
)
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
class Constants(SimpleNamespace):
|
|
@@ -17,7 +21,7 @@ class Constants(SimpleNamespace):
|
|
|
17
21
|
# The Tailwind config.
|
|
18
22
|
CONFIG = "tailwind.config.js"
|
|
19
23
|
# Default Tailwind content paths
|
|
20
|
-
CONTENT = ["./
|
|
24
|
+
CONTENT = [f"./{Dirs.PAGES}/**/*.{{js,ts,jsx,tsx}}", "./utils/**/*.{js,ts,jsx,tsx}"]
|
|
21
25
|
# Relative tailwind style path to root stylesheet in Dirs.STYLES.
|
|
22
26
|
ROOT_STYLE_PATH = "./tailwind.css"
|
|
23
27
|
|
|
@@ -35,90 +39,7 @@ class Constants(SimpleNamespace):
|
|
|
35
39
|
TAILWIND_CSS = "@import url('./tailwind.css');"
|
|
36
40
|
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
def tailwind_config_js_template():
|
|
40
|
-
"""Get the Tailwind config template.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
The Tailwind config template.
|
|
44
|
-
"""
|
|
45
|
-
from reflex.compiler.templates import from_string
|
|
46
|
-
|
|
47
|
-
source = r"""
|
|
48
|
-
{# Helper macro to render JS objects and arrays #}
|
|
49
|
-
{% macro render_js(val, indent=2, level=0) -%}
|
|
50
|
-
{%- set space = ' ' * (indent * level) -%}
|
|
51
|
-
{%- set next_space = ' ' * (indent * (level + 1)) -%}
|
|
52
|
-
|
|
53
|
-
{%- if val is mapping -%}
|
|
54
|
-
{
|
|
55
|
-
{%- for k, v in val.items() %}
|
|
56
|
-
{{ next_space }}{{ k if k is string and k.isidentifier() else k|tojson }}: {{ render_js(v, indent, level + 1) }}{{ "," if not loop.last }}
|
|
57
|
-
{%- endfor %}
|
|
58
|
-
{{ space }}}
|
|
59
|
-
{%- elif val is iterable and val is not string -%}
|
|
60
|
-
[
|
|
61
|
-
{%- for item in val %}
|
|
62
|
-
{{ next_space }}{{ render_js(item, indent, level + 1) }}{{ "," if not loop.last }}
|
|
63
|
-
{%- endfor %}
|
|
64
|
-
{{ space }}]
|
|
65
|
-
{%- else -%}
|
|
66
|
-
{{ val | tojson }}
|
|
67
|
-
{%- endif -%}
|
|
68
|
-
{%- endmacro %}
|
|
69
|
-
|
|
70
|
-
{# Extract destructured imports from plugin dicts only #}
|
|
71
|
-
{%- set imports = [] %}
|
|
72
|
-
{%- for plugin in plugins if plugin is mapping and plugin.import is defined %}
|
|
73
|
-
{%- set _ = imports.append(plugin.import) %}
|
|
74
|
-
{%- endfor %}
|
|
75
|
-
|
|
76
|
-
/** @type {import('tailwindcss').Config} */
|
|
77
|
-
{%- for imp in imports %}
|
|
78
|
-
const { {{ imp.name }} } = require({{ imp.from | tojson }});
|
|
79
|
-
{%- endfor %}
|
|
80
|
-
|
|
81
|
-
module.exports = {
|
|
82
|
-
content: {{ render_js(content) }},
|
|
83
|
-
theme: {{ render_js(theme) }},
|
|
84
|
-
{% if darkMode is defined %}darkMode: {{ darkMode | tojson }},{% endif %}
|
|
85
|
-
{% if corePlugins is defined %}corePlugins: {{ render_js(corePlugins) }},{% endif %}
|
|
86
|
-
{% if important is defined %}important: {{ important | tojson }},{% endif %}
|
|
87
|
-
{% if prefix is defined %}prefix: {{ prefix | tojson }},{% endif %}
|
|
88
|
-
{% if separator is defined %}separator: {{ separator | tojson }},{% endif %}
|
|
89
|
-
{% if presets is defined %}
|
|
90
|
-
presets: [
|
|
91
|
-
{% for preset in presets %}
|
|
92
|
-
require({{ preset | tojson }}){{ "," if not loop.last }}
|
|
93
|
-
{% endfor %}
|
|
94
|
-
],
|
|
95
|
-
{% endif %}
|
|
96
|
-
plugins: [
|
|
97
|
-
{% for plugin in plugins %}
|
|
98
|
-
{% if plugin is mapping %}
|
|
99
|
-
{% if plugin.call is defined %}
|
|
100
|
-
{{ plugin.call }}(
|
|
101
|
-
{%- if plugin.args is defined -%}
|
|
102
|
-
{{ render_js(plugin.args) }}
|
|
103
|
-
{%- endif -%}
|
|
104
|
-
){{ "," if not loop.last }}
|
|
105
|
-
{% else %}
|
|
106
|
-
require({{ plugin.name | tojson }}){{ "," if not loop.last }}
|
|
107
|
-
{% endif %}
|
|
108
|
-
{% else %}
|
|
109
|
-
require({{ plugin | tojson }}){{ "," if not loop.last }}
|
|
110
|
-
{% endif %}
|
|
111
|
-
{% endfor %}
|
|
112
|
-
]
|
|
113
|
-
};
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
return from_string(source)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def compile_config(
|
|
120
|
-
config: dict,
|
|
121
|
-
):
|
|
42
|
+
def compile_config(config: TailwindConfig):
|
|
122
43
|
"""Compile the Tailwind config.
|
|
123
44
|
|
|
124
45
|
Args:
|
|
@@ -129,6 +50,7 @@ def compile_config(
|
|
|
129
50
|
"""
|
|
130
51
|
return Constants.CONFIG, tailwind_config_js_template().render(
|
|
131
52
|
**config,
|
|
53
|
+
DEFAULT_CONTENT=Constants.CONTENT,
|
|
132
54
|
)
|
|
133
55
|
|
|
134
56
|
|
|
@@ -215,7 +137,8 @@ def add_tailwind_to_css_file(css_file_content: str) -> str:
|
|
|
215
137
|
)
|
|
216
138
|
|
|
217
139
|
|
|
218
|
-
|
|
140
|
+
@dataclasses.dataclass
|
|
141
|
+
class TailwindV3Plugin(TailwindPlugin):
|
|
219
142
|
"""Plugin for Tailwind CSS."""
|
|
220
143
|
|
|
221
144
|
def get_frontend_development_dependencies(self, **context) -> list[str]:
|
|
@@ -227,13 +150,10 @@ class Plugin(PluginBase):
|
|
|
227
150
|
Returns:
|
|
228
151
|
A list of packages required by the plugin.
|
|
229
152
|
"""
|
|
230
|
-
from reflex.config import get_config
|
|
231
|
-
|
|
232
|
-
config = get_config()
|
|
233
153
|
return [
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
]
|
|
154
|
+
*super().get_frontend_development_dependencies(**context),
|
|
155
|
+
Constants.VERSION,
|
|
156
|
+
]
|
|
237
157
|
|
|
238
158
|
def pre_compile(self, **context):
|
|
239
159
|
"""Pre-compile the plugin.
|
|
@@ -241,23 +161,10 @@ class Plugin(PluginBase):
|
|
|
241
161
|
Args:
|
|
242
162
|
context: The context for the plugin.
|
|
243
163
|
"""
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
config = get_config().tailwind or {}
|
|
247
|
-
|
|
248
|
-
config["content"] = config.get("content", Constants.CONTENT)
|
|
249
|
-
context["add_save_task"](compile_config, config)
|
|
164
|
+
context["add_save_task"](compile_config, self.get_unversioned_config())
|
|
250
165
|
context["add_save_task"](compile_root_style)
|
|
251
166
|
context["add_modify_task"](Dirs.POSTCSS_JS, add_tailwind_to_postcss_config)
|
|
252
167
|
context["add_modify_task"](
|
|
253
168
|
str(Path(Dirs.STYLES) / (PageNames.STYLESHEET_ROOT + Ext.CSS)),
|
|
254
169
|
add_tailwind_to_css_file,
|
|
255
170
|
)
|
|
256
|
-
|
|
257
|
-
def __repr__(self):
|
|
258
|
-
"""Return a string representation of the plugin.
|
|
259
|
-
|
|
260
|
-
Returns:
|
|
261
|
-
A string representation of the plugin.
|
|
262
|
-
"""
|
|
263
|
-
return "TailwindV3Plugin()"
|
reflex/plugins/tailwind_v4.py
CHANGED
|
@@ -1,123 +1,44 @@
|
|
|
1
1
|
"""Base class for all plugins."""
|
|
2
2
|
|
|
3
|
+
import dataclasses
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from types import SimpleNamespace
|
|
5
6
|
|
|
6
7
|
from reflex.constants.base import Dirs
|
|
7
8
|
from reflex.constants.compiler import Ext, PageNames
|
|
8
|
-
from reflex.plugins.
|
|
9
|
-
|
|
9
|
+
from reflex.plugins.shared_tailwind import (
|
|
10
|
+
TailwindConfig,
|
|
11
|
+
TailwindPlugin,
|
|
12
|
+
tailwind_config_js_template,
|
|
13
|
+
)
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
class Constants(SimpleNamespace):
|
|
13
17
|
"""Tailwind constants."""
|
|
14
18
|
|
|
15
19
|
# The Tailwindcss version
|
|
16
|
-
VERSION = "tailwindcss@4.1.
|
|
20
|
+
VERSION = "tailwindcss@4.1.10"
|
|
17
21
|
# The Tailwind config.
|
|
18
22
|
CONFIG = "tailwind.config.js"
|
|
19
23
|
# Default Tailwind content paths
|
|
20
|
-
CONTENT = ["./
|
|
24
|
+
CONTENT = [f"./{Dirs.PAGES}/**/*.{{js,ts,jsx,tsx}}", "./utils/**/*.{js,ts,jsx,tsx}"]
|
|
21
25
|
# Relative tailwind style path to root stylesheet in Dirs.STYLES.
|
|
22
26
|
ROOT_STYLE_PATH = "./tailwind.css"
|
|
23
27
|
|
|
24
28
|
# Content of the style content.
|
|
25
29
|
ROOT_STYLE_CONTENT = """@layer theme, base, components, utilities;
|
|
26
|
-
@config "../tailwind.config.js";
|
|
27
30
|
@import "tailwindcss/theme.css" layer(theme);
|
|
28
31
|
@import "tailwindcss/preflight.css" layer(base);
|
|
29
32
|
@import "{radix_url}" layer(components);
|
|
30
33
|
@import "tailwindcss/utilities.css" layer(utilities);
|
|
34
|
+
@config "../tailwind.config.js";
|
|
31
35
|
"""
|
|
32
36
|
|
|
33
37
|
# The default tailwind css.
|
|
34
38
|
TAILWIND_CSS = "@import url('./tailwind.css');"
|
|
35
39
|
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
def tailwind_config_js_template():
|
|
39
|
-
"""Get the Tailwind config template.
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
The Tailwind config template.
|
|
43
|
-
"""
|
|
44
|
-
from reflex.compiler.templates import from_string
|
|
45
|
-
|
|
46
|
-
source = r"""
|
|
47
|
-
{# Helper macro to render JS objects and arrays #}
|
|
48
|
-
{% macro render_js(val, indent=2, level=0) -%}
|
|
49
|
-
{%- set space = ' ' * (indent * level) -%}
|
|
50
|
-
{%- set next_space = ' ' * (indent * (level + 1)) -%}
|
|
51
|
-
|
|
52
|
-
{%- if val is mapping -%}
|
|
53
|
-
{
|
|
54
|
-
{%- for k, v in val.items() %}
|
|
55
|
-
{{ next_space }}{{ k if k is string and k.isidentifier() else k|tojson }}: {{ render_js(v, indent, level + 1) }}{{ "," if not loop.last }}
|
|
56
|
-
{%- endfor %}
|
|
57
|
-
{{ space }}}
|
|
58
|
-
{%- elif val is iterable and val is not string -%}
|
|
59
|
-
[
|
|
60
|
-
{%- for item in val %}
|
|
61
|
-
{{ next_space }}{{ render_js(item, indent, level + 1) }}{{ "," if not loop.last }}
|
|
62
|
-
{%- endfor %}
|
|
63
|
-
{{ space }}]
|
|
64
|
-
{%- else -%}
|
|
65
|
-
{{ val | tojson }}
|
|
66
|
-
{%- endif -%}
|
|
67
|
-
{%- endmacro %}
|
|
68
|
-
|
|
69
|
-
{# Extract destructured imports from plugin dicts only #}
|
|
70
|
-
{%- set imports = [] %}
|
|
71
|
-
{%- for plugin in plugins if plugin is mapping and plugin.import is defined %}
|
|
72
|
-
{%- set _ = imports.append(plugin.import) %}
|
|
73
|
-
{%- endfor %}
|
|
74
|
-
|
|
75
|
-
/** @type {import('tailwindcss').Config} */
|
|
76
|
-
{%- for imp in imports %}
|
|
77
|
-
const { {{ imp.name }} } = require({{ imp.from | tojson }});
|
|
78
|
-
{%- endfor %}
|
|
79
|
-
|
|
80
|
-
module.exports = {
|
|
81
|
-
content: {{ render_js(content) }},
|
|
82
|
-
theme: {{ render_js(theme) }},
|
|
83
|
-
{% if darkMode is defined %}darkMode: {{ darkMode | tojson }},{% endif %}
|
|
84
|
-
{% if corePlugins is defined %}corePlugins: {{ render_js(corePlugins) }},{% endif %}
|
|
85
|
-
{% if important is defined %}important: {{ important | tojson }},{% endif %}
|
|
86
|
-
{% if prefix is defined %}prefix: {{ prefix | tojson }},{% endif %}
|
|
87
|
-
{% if separator is defined %}separator: {{ separator | tojson }},{% endif %}
|
|
88
|
-
{% if presets is defined %}
|
|
89
|
-
presets: [
|
|
90
|
-
{% for preset in presets %}
|
|
91
|
-
require({{ preset | tojson }}){{ "," if not loop.last }}
|
|
92
|
-
{% endfor %}
|
|
93
|
-
],
|
|
94
|
-
{% endif %}
|
|
95
|
-
plugins: [
|
|
96
|
-
{% for plugin in plugins %}
|
|
97
|
-
{% if plugin is mapping %}
|
|
98
|
-
{% if plugin.call is defined %}
|
|
99
|
-
{{ plugin.call }}(
|
|
100
|
-
{%- if plugin.args is defined -%}
|
|
101
|
-
{{ render_js(plugin.args) }}
|
|
102
|
-
{%- endif -%}
|
|
103
|
-
){{ "," if not loop.last }}
|
|
104
|
-
{% else %}
|
|
105
|
-
require({{ plugin.name | tojson }}){{ "," if not loop.last }}
|
|
106
|
-
{% endif %}
|
|
107
|
-
{% else %}
|
|
108
|
-
require({{ plugin | tojson }}){{ "," if not loop.last }}
|
|
109
|
-
{% endif %}
|
|
110
|
-
{% endfor %}
|
|
111
|
-
]
|
|
112
|
-
};
|
|
113
|
-
"""
|
|
114
|
-
|
|
115
|
-
return from_string(source)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def compile_config(
|
|
119
|
-
config: dict,
|
|
120
|
-
):
|
|
41
|
+
def compile_config(config: TailwindConfig):
|
|
121
42
|
"""Compile the Tailwind config.
|
|
122
43
|
|
|
123
44
|
Args:
|
|
@@ -128,6 +49,7 @@ def compile_config(
|
|
|
128
49
|
"""
|
|
129
50
|
return Constants.CONFIG, tailwind_config_js_template().render(
|
|
130
51
|
**config,
|
|
52
|
+
DEFAULT_CONTENT=Constants.CONTENT,
|
|
131
53
|
)
|
|
132
54
|
|
|
133
55
|
|
|
@@ -218,7 +140,8 @@ def add_tailwind_to_css_file(css_file_content: str) -> str:
|
|
|
218
140
|
)
|
|
219
141
|
|
|
220
142
|
|
|
221
|
-
|
|
143
|
+
@dataclasses.dataclass
|
|
144
|
+
class TailwindV4Plugin(TailwindPlugin):
|
|
222
145
|
"""Plugin for Tailwind CSS."""
|
|
223
146
|
|
|
224
147
|
def get_frontend_development_dependencies(self, **context) -> list[str]:
|
|
@@ -230,13 +153,11 @@ class Plugin(PluginBase):
|
|
|
230
153
|
Returns:
|
|
231
154
|
A list of packages required by the plugin.
|
|
232
155
|
"""
|
|
233
|
-
from reflex.config import get_config
|
|
234
|
-
|
|
235
|
-
config = get_config()
|
|
236
156
|
return [
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
157
|
+
*super().get_frontend_development_dependencies(**context),
|
|
158
|
+
Constants.VERSION,
|
|
159
|
+
"@tailwindcss/postcss@4.1.10",
|
|
160
|
+
]
|
|
240
161
|
|
|
241
162
|
def pre_compile(self, **context):
|
|
242
163
|
"""Pre-compile the plugin.
|
|
@@ -244,23 +165,10 @@ class Plugin(PluginBase):
|
|
|
244
165
|
Args:
|
|
245
166
|
context: The context for the plugin.
|
|
246
167
|
"""
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
config = get_config().tailwind or {}
|
|
250
|
-
|
|
251
|
-
config["content"] = config.get("content", Constants.CONTENT)
|
|
252
|
-
context["add_save_task"](compile_config, config)
|
|
168
|
+
context["add_save_task"](compile_config, self.get_unversioned_config())
|
|
253
169
|
context["add_save_task"](compile_root_style)
|
|
254
170
|
context["add_modify_task"](Dirs.POSTCSS_JS, add_tailwind_to_postcss_config)
|
|
255
171
|
context["add_modify_task"](
|
|
256
172
|
str(Path(Dirs.STYLES) / (PageNames.STYLESHEET_ROOT + Ext.CSS)),
|
|
257
173
|
add_tailwind_to_css_file,
|
|
258
174
|
)
|
|
259
|
-
|
|
260
|
-
def __repr__(self):
|
|
261
|
-
"""Return a string representation of the plugin.
|
|
262
|
-
|
|
263
|
-
Returns:
|
|
264
|
-
A string representation of the plugin.
|
|
265
|
-
"""
|
|
266
|
-
return "TailwindV4Plugin()"
|
reflex/reflex.py
CHANGED
reflex/route.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
|
|
7
8
|
from reflex import constants
|
|
8
9
|
|
|
@@ -16,15 +17,40 @@ def verify_route_validity(route: str) -> None:
|
|
|
16
17
|
Raises:
|
|
17
18
|
ValueError: If the route is invalid.
|
|
18
19
|
"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
route_parts = route.removeprefix("/").split("/")
|
|
21
|
+
for i, part in enumerate(route_parts):
|
|
22
|
+
if constants.RouteRegex.SLUG.fullmatch(part):
|
|
23
|
+
continue
|
|
24
|
+
if not part.startswith("[") or not part.endswith("]"):
|
|
25
|
+
msg = (
|
|
26
|
+
f"Route part `{part}` is not valid. Reflex only supports "
|
|
27
|
+
"alphabetic characters, underscores, and hyphens in route parts. "
|
|
28
|
+
)
|
|
29
|
+
raise ValueError(msg)
|
|
30
|
+
if part.startswith(("[[...", "[...")):
|
|
31
|
+
if part != constants.RouteRegex.SPLAT_CATCHALL:
|
|
32
|
+
msg = f"Catchall pattern `{part}` is not valid. Only `{constants.RouteRegex.SPLAT_CATCHALL}` is allowed."
|
|
33
|
+
raise ValueError(msg)
|
|
34
|
+
if i != len(route_parts) - 1:
|
|
35
|
+
msg = f"Catchall pattern `{part}` must be at the end of the route."
|
|
36
|
+
raise ValueError(msg)
|
|
37
|
+
continue
|
|
38
|
+
if part.startswith("[["):
|
|
39
|
+
if constants.RouteRegex.OPTIONAL_ARG.fullmatch(part):
|
|
40
|
+
continue
|
|
41
|
+
msg = (
|
|
42
|
+
f"Route part `{part}` with optional argument is not valid. "
|
|
43
|
+
"Reflex only supports optional arguments that start with an alphabetic character or underscore, "
|
|
44
|
+
"followed by alphanumeric characters or underscores."
|
|
45
|
+
)
|
|
46
|
+
raise ValueError(msg)
|
|
47
|
+
if not constants.RouteRegex.ARG.fullmatch(part):
|
|
48
|
+
msg = (
|
|
49
|
+
f"Route part `{part}` with argument is not valid. "
|
|
50
|
+
"Reflex only supports argument names that start with an alphabetic character or underscore, "
|
|
51
|
+
"followed by alphanumeric characters or underscores."
|
|
52
|
+
)
|
|
53
|
+
raise ValueError(msg)
|
|
28
54
|
|
|
29
55
|
|
|
30
56
|
def get_route_args(route: str) -> dict[str, str]:
|
|
@@ -38,103 +64,159 @@ def get_route_args(route: str) -> dict[str, str]:
|
|
|
38
64
|
"""
|
|
39
65
|
args = {}
|
|
40
66
|
|
|
41
|
-
def
|
|
42
|
-
"""Add arg from regex search result.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
match: Result of a regex search
|
|
46
|
-
type_: The assigned type for this arg
|
|
47
|
-
|
|
48
|
-
Raises:
|
|
49
|
-
ValueError: If the route is invalid.
|
|
50
|
-
"""
|
|
51
|
-
arg_name = match.groups()[0]
|
|
67
|
+
def _add_route_arg(arg_name: str, type_: str):
|
|
52
68
|
if arg_name in args:
|
|
53
|
-
msg =
|
|
69
|
+
msg = (
|
|
70
|
+
f"Arg name `{arg_name}` is used more than once in the route `{route}`."
|
|
71
|
+
)
|
|
54
72
|
raise ValueError(msg)
|
|
55
73
|
args[arg_name] = type_
|
|
56
74
|
|
|
57
75
|
# Regex to check for route args.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
check_opt_catchall = constants.RouteRegex.OPT_CATCHALL
|
|
76
|
+
argument_regex = constants.RouteRegex.ARG
|
|
77
|
+
optional_argument_regex = constants.RouteRegex.OPTIONAL_ARG
|
|
61
78
|
|
|
62
79
|
# Iterate over the route parts and check for route args.
|
|
63
80
|
for part in route.split("/"):
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
add_route_arg(match_opt, constants.RouteArgType.LIST)
|
|
81
|
+
if part == constants.RouteRegex.SPLAT_CATCHALL:
|
|
82
|
+
_add_route_arg("splat", constants.RouteArgType.LIST)
|
|
67
83
|
break
|
|
68
84
|
|
|
69
|
-
|
|
70
|
-
if
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
optional_argument = optional_argument_regex.match(part)
|
|
86
|
+
if optional_argument:
|
|
87
|
+
_add_route_arg(optional_argument.group(1), constants.RouteArgType.SINGLE)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
argument = argument_regex.match(part)
|
|
91
|
+
if argument:
|
|
92
|
+
_add_route_arg(argument.group(1), constants.RouteArgType.SINGLE)
|
|
93
|
+
continue
|
|
73
94
|
|
|
74
|
-
match = check.match(part)
|
|
75
|
-
if match:
|
|
76
|
-
# Add the route arg to the list.
|
|
77
|
-
add_route_arg(match, constants.RouteArgType.SINGLE)
|
|
78
95
|
return args
|
|
79
96
|
|
|
80
97
|
|
|
81
|
-
def
|
|
82
|
-
"""
|
|
98
|
+
def replace_brackets_with_keywords(input_string: str) -> str:
|
|
99
|
+
"""Replace brackets and everything inside it in a string with a keyword.
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
>>> replace_brackets_with_keywords("/posts")
|
|
103
|
+
'/posts'
|
|
104
|
+
>>> replace_brackets_with_keywords("/posts/[slug]")
|
|
105
|
+
'/posts/__SINGLE_SEGMENT__'
|
|
106
|
+
>>> replace_brackets_with_keywords("/posts/[slug]/comments")
|
|
107
|
+
'/posts/__SINGLE_SEGMENT__/comments'
|
|
108
|
+
>>> replace_brackets_with_keywords("/posts/[[slug]]")
|
|
109
|
+
'/posts/__DOUBLE_SEGMENT__'
|
|
110
|
+
>>> replace_brackets_with_keywords("/posts/[[...splat]]")
|
|
111
|
+
'/posts/__DOUBLE_CATCHALL_SEGMENT__'
|
|
83
112
|
|
|
84
113
|
Args:
|
|
85
|
-
|
|
114
|
+
input_string: String to replace.
|
|
86
115
|
|
|
87
116
|
Returns:
|
|
88
|
-
|
|
117
|
+
new string containing keywords.
|
|
89
118
|
"""
|
|
90
|
-
|
|
91
|
-
return
|
|
119
|
+
# Replace [<slug>] with __SINGLE_SEGMENT__
|
|
120
|
+
return constants.RouteRegex.ARG.sub(
|
|
121
|
+
constants.RouteRegex.SINGLE_SEGMENT,
|
|
122
|
+
# Replace [[slug]] with __DOUBLE_SEGMENT__
|
|
123
|
+
constants.RouteRegex.OPTIONAL_ARG.sub(
|
|
124
|
+
constants.RouteRegex.DOUBLE_SEGMENT,
|
|
125
|
+
# Replace [[...splat]] with __DOUBLE_CATCHALL_SEGMENT__
|
|
126
|
+
input_string.replace(
|
|
127
|
+
constants.RouteRegex.SPLAT_CATCHALL,
|
|
128
|
+
constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT,
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
)
|
|
92
132
|
|
|
93
133
|
|
|
94
|
-
def
|
|
95
|
-
"""
|
|
134
|
+
def route_specifity(keyworded_route: str) -> tuple[int, int, int]:
|
|
135
|
+
"""Get the specificity of a route with keywords.
|
|
136
|
+
|
|
137
|
+
The smaller the number, the more specific the route is.
|
|
96
138
|
|
|
97
139
|
Args:
|
|
98
|
-
|
|
140
|
+
keyworded_route: The route with keywords.
|
|
99
141
|
|
|
100
142
|
Returns:
|
|
101
|
-
|
|
143
|
+
A tuple containing the counts of double catchall segments,
|
|
144
|
+
double segments, and single segments in the route.
|
|
102
145
|
"""
|
|
103
|
-
|
|
104
|
-
|
|
146
|
+
return (
|
|
147
|
+
keyworded_route.count(constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT),
|
|
148
|
+
keyworded_route.count(constants.RouteRegex.DOUBLE_SEGMENT),
|
|
149
|
+
keyworded_route.count(constants.RouteRegex.SINGLE_SEGMENT),
|
|
150
|
+
)
|
|
105
151
|
|
|
106
152
|
|
|
107
|
-
def
|
|
108
|
-
"""
|
|
153
|
+
def get_route_regex(keyworded_route: str) -> re.Pattern:
|
|
154
|
+
"""Get the regex pattern for a route with keywords.
|
|
109
155
|
|
|
110
156
|
Args:
|
|
111
|
-
|
|
157
|
+
keyworded_route: The route with keywords.
|
|
112
158
|
|
|
113
159
|
Returns:
|
|
114
|
-
|
|
160
|
+
A compiled regex pattern for the route.
|
|
115
161
|
"""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
if keyworded_route == "index":
|
|
163
|
+
return re.compile(re.escape("/"))
|
|
164
|
+
path_parts = keyworded_route.split("/")
|
|
165
|
+
regex_parts = []
|
|
166
|
+
for part in path_parts:
|
|
167
|
+
if part == constants.RouteRegex.SINGLE_SEGMENT:
|
|
168
|
+
# Match a single segment (/slug)
|
|
169
|
+
regex_parts.append(r"/[^/]*")
|
|
170
|
+
elif part == constants.RouteRegex.DOUBLE_SEGMENT:
|
|
171
|
+
# Match a single optional segment (/slug or nothing)
|
|
172
|
+
regex_parts.append(r"(/[^/]+)?")
|
|
173
|
+
elif part == constants.RouteRegex.DOUBLE_CATCHALL_SEGMENT:
|
|
174
|
+
regex_parts.append(".*")
|
|
175
|
+
else:
|
|
176
|
+
regex_parts.append(re.escape("/" + part))
|
|
177
|
+
# Join the regex parts and compile the regex
|
|
178
|
+
regex_pattern = "".join(regex_parts)
|
|
179
|
+
regex_pattern = f"^{regex_pattern}/?$"
|
|
180
|
+
return re.compile(regex_pattern)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_router(routes: list[str]) -> Callable[[str], str | None]:
|
|
184
|
+
"""Get a function that computes the route for a given path.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
routes: A list of routes to match against.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
A function that takes a path and returns the first matching route,
|
|
191
|
+
or None if no match is found.
|
|
192
|
+
"""
|
|
193
|
+
keyworded_routes = {
|
|
194
|
+
replace_brackets_with_keywords(route): route for route in routes
|
|
195
|
+
}
|
|
196
|
+
sorted_routes_by_specifity = sorted(
|
|
197
|
+
keyworded_routes.items(),
|
|
198
|
+
key=lambda item: route_specifity(item[0]),
|
|
138
199
|
)
|
|
139
|
-
|
|
140
|
-
|
|
200
|
+
regexed_routes = [
|
|
201
|
+
(get_route_regex(keyworded_route), original_route)
|
|
202
|
+
for keyworded_route, original_route in sorted_routes_by_specifity
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
def get_route(path: str) -> str | None:
|
|
206
|
+
"""Get the first matching route for a given path.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
path: The path to match against the routes.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
The first matching route, or None if no match is found.
|
|
213
|
+
"""
|
|
214
|
+
path = "/" + path.removeprefix("/").removesuffix("/")
|
|
215
|
+
if path == "/index":
|
|
216
|
+
path = "/"
|
|
217
|
+
for regex, original_route in regexed_routes:
|
|
218
|
+
if regex.fullmatch(path):
|
|
219
|
+
return original_route
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
return get_route
|