reflex 0.7.12a1__py3-none-any.whl → 0.7.13__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 +1 -0
- reflex/.templates/web/postcss.config.js +0 -1
- reflex/.templates/web/utils/state.js +1 -1
- reflex/__init__.py +1 -0
- reflex/__init__.pyi +1 -0
- reflex/app.py +101 -29
- reflex/compiler/compiler.py +11 -62
- reflex/compiler/templates.py +12 -3
- reflex/compiler/utils.py +20 -4
- reflex/components/component.py +366 -88
- reflex/components/core/helmet.pyi +66 -0
- reflex/components/datadisplay/code.py +1 -1
- reflex/components/datadisplay/shiki_code_block.py +97 -86
- reflex/components/datadisplay/shiki_code_block.pyi +4 -2
- reflex/components/el/elements/forms.py +1 -1
- reflex/components/lucide/icon.py +2 -1
- reflex/components/lucide/icon.pyi +1 -0
- reflex/components/plotly/plotly.py +2 -2
- reflex/components/plotly/plotly.pyi +2 -3
- reflex/components/radix/primitives/accordion.py +1 -1
- reflex/components/radix/primitives/drawer.py +1 -1
- reflex/components/radix/primitives/form.py +1 -1
- reflex/components/radix/themes/base.py +4 -11
- reflex/components/radix/themes/components/icon_button.py +2 -2
- reflex/components/radix/themes/components/text_field.py +3 -0
- reflex/components/radix/themes/components/text_field.pyi +2 -0
- reflex/components/radix/themes/layout/list.py +1 -1
- reflex/components/tags/iter_tag.py +3 -5
- reflex/config.py +57 -7
- reflex/constants/__init__.py +0 -2
- reflex/event.py +154 -93
- reflex/experimental/client_state.py +3 -1
- reflex/plugins/__init__.py +7 -0
- reflex/plugins/base.py +101 -0
- reflex/plugins/tailwind_v3.py +255 -0
- reflex/plugins/tailwind_v4.py +258 -0
- reflex/state.py +24 -3
- reflex/utils/build.py +1 -1
- reflex/utils/console.py +1 -1
- reflex/utils/exec.py +23 -0
- reflex/utils/path_ops.py +26 -6
- reflex/utils/prerequisites.py +21 -90
- reflex/utils/pyi_generator.py +12 -2
- reflex/utils/types.py +15 -1
- reflex/vars/base.py +59 -4
- reflex/vars/object.py +8 -0
- {reflex-0.7.12a1.dist-info → reflex-0.7.13.dist-info}/METADATA +2 -2
- {reflex-0.7.12a1.dist-info → reflex-0.7.13.dist-info}/RECORD +52 -50
- scripts/hatch_build.py +17 -0
- reflex/.templates/jinja/web/tailwind.config.js.jinja2 +0 -66
- reflex/.templates/web/styles/tailwind.css +0 -6
- reflex/constants/style.py +0 -16
- {reflex-0.7.12a1.dist-info → reflex-0.7.13.dist-info}/WHEEL +0 -0
- {reflex-0.7.12a1.dist-info → reflex-0.7.13.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.12a1.dist-info → reflex-0.7.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Base class for all plugins."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
from reflex.constants.base import Dirs
|
|
7
|
+
from reflex.constants.compiler import Ext, PageNames
|
|
8
|
+
from reflex.plugins.base import Plugin as PluginBase
|
|
9
|
+
from reflex.utils.decorator import once
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Constants(SimpleNamespace):
|
|
13
|
+
"""Tailwind constants."""
|
|
14
|
+
|
|
15
|
+
# The Tailwindcss version
|
|
16
|
+
VERSION = "tailwindcss@3.4.17"
|
|
17
|
+
# The Tailwind config.
|
|
18
|
+
CONFIG = "tailwind.config.js"
|
|
19
|
+
# Default Tailwind content paths
|
|
20
|
+
CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"]
|
|
21
|
+
# Relative tailwind style path to root stylesheet in Dirs.STYLES.
|
|
22
|
+
ROOT_STYLE_PATH = "./tailwind.css"
|
|
23
|
+
|
|
24
|
+
# Content of the style content.
|
|
25
|
+
ROOT_STYLE_CONTENT = """
|
|
26
|
+
@import "tailwindcss/base";
|
|
27
|
+
|
|
28
|
+
@import url('{radix_url}');
|
|
29
|
+
|
|
30
|
+
@tailwind components;
|
|
31
|
+
@tailwind utilities;
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# The default tailwind css.
|
|
35
|
+
TAILWIND_CSS = "@import url('./tailwind.css');"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@once
|
|
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
|
+
):
|
|
122
|
+
"""Compile the Tailwind config.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
config: The Tailwind config.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The compiled Tailwind config.
|
|
129
|
+
"""
|
|
130
|
+
return Constants.CONFIG, tailwind_config_js_template().render(
|
|
131
|
+
**config,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def compile_root_style():
|
|
136
|
+
"""Compile the Tailwind root style.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The compiled Tailwind root style.
|
|
140
|
+
"""
|
|
141
|
+
from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET
|
|
142
|
+
|
|
143
|
+
return str(
|
|
144
|
+
Path(Dirs.STYLES) / Constants.ROOT_STYLE_PATH
|
|
145
|
+
), Constants.ROOT_STYLE_CONTENT.format(
|
|
146
|
+
radix_url=RADIX_THEMES_STYLESHEET,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _index_of_element_that_has(haystack: list[str], needle: str) -> int | None:
|
|
151
|
+
return next(
|
|
152
|
+
(i for i, line in enumerate(haystack) if needle in line),
|
|
153
|
+
None,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def add_tailwind_to_postcss_config(postcss_file_content: str) -> str:
|
|
158
|
+
"""Add tailwind to the postcss config.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
postcss_file_content: The content of the postcss config file.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
The modified postcss config file content.
|
|
165
|
+
"""
|
|
166
|
+
from reflex.constants import Dirs
|
|
167
|
+
|
|
168
|
+
postcss_file_lines = postcss_file_content.splitlines()
|
|
169
|
+
|
|
170
|
+
if _index_of_element_that_has(postcss_file_lines, "tailwindcss") is not None:
|
|
171
|
+
return postcss_file_content
|
|
172
|
+
|
|
173
|
+
line_with_postcss_plugins = _index_of_element_that_has(
|
|
174
|
+
postcss_file_lines, "plugins"
|
|
175
|
+
)
|
|
176
|
+
if not line_with_postcss_plugins:
|
|
177
|
+
print( # noqa: T201
|
|
178
|
+
f"Could not find line with 'plugins' in {Dirs.POSTCSS_JS}. "
|
|
179
|
+
"Please make sure the file exists and is valid."
|
|
180
|
+
)
|
|
181
|
+
return postcss_file_content
|
|
182
|
+
|
|
183
|
+
postcss_import_line = _index_of_element_that_has(
|
|
184
|
+
postcss_file_lines, '"postcss-import"'
|
|
185
|
+
)
|
|
186
|
+
postcss_file_lines.insert(
|
|
187
|
+
(postcss_import_line or line_with_postcss_plugins) + 1, "tailwindcss: {},"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return "\n".join(postcss_file_lines)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def add_tailwind_to_css_file(css_file_content: str) -> str:
|
|
194
|
+
"""Add tailwind to the css file.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
css_file_content: The content of the css file.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The modified css file content.
|
|
201
|
+
"""
|
|
202
|
+
from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET
|
|
203
|
+
|
|
204
|
+
if Constants.TAILWIND_CSS.splitlines()[0] in css_file_content:
|
|
205
|
+
return css_file_content
|
|
206
|
+
if RADIX_THEMES_STYLESHEET not in css_file_content:
|
|
207
|
+
print( # noqa: T201
|
|
208
|
+
f"Could not find line with '{RADIX_THEMES_STYLESHEET}' in {Dirs.STYLES}. "
|
|
209
|
+
"Please make sure the file exists and is valid."
|
|
210
|
+
)
|
|
211
|
+
return css_file_content
|
|
212
|
+
return css_file_content.replace(
|
|
213
|
+
f"@import url('{RADIX_THEMES_STYLESHEET}');",
|
|
214
|
+
Constants.TAILWIND_CSS,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class Plugin(PluginBase):
|
|
219
|
+
"""Plugin for Tailwind CSS."""
|
|
220
|
+
|
|
221
|
+
def get_frontend_development_dependencies(self, **context) -> list[str]:
|
|
222
|
+
"""Get the packages required by the plugin.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
**context: The context for the plugin.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
A list of packages required by the plugin.
|
|
229
|
+
"""
|
|
230
|
+
from reflex.config import get_config
|
|
231
|
+
|
|
232
|
+
config = get_config()
|
|
233
|
+
return [
|
|
234
|
+
plugin if isinstance(plugin, str) else plugin.get("name")
|
|
235
|
+
for plugin in (config.tailwind or {}).get("plugins", [])
|
|
236
|
+
] + [Constants.VERSION]
|
|
237
|
+
|
|
238
|
+
def pre_compile(self, **context):
|
|
239
|
+
"""Pre-compile the plugin.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
context: The context for the plugin.
|
|
243
|
+
"""
|
|
244
|
+
from reflex.config import get_config
|
|
245
|
+
|
|
246
|
+
config = get_config().tailwind or {}
|
|
247
|
+
|
|
248
|
+
config["content"] = config.get("content", Constants.CONTENT)
|
|
249
|
+
context["add_save_task"](compile_config, config)
|
|
250
|
+
context["add_save_task"](compile_root_style)
|
|
251
|
+
context["add_modify_task"](Dirs.POSTCSS_JS, add_tailwind_to_postcss_config)
|
|
252
|
+
context["add_modify_task"](
|
|
253
|
+
str(Path(Dirs.STYLES) / (PageNames.STYLESHEET_ROOT + Ext.CSS)),
|
|
254
|
+
add_tailwind_to_css_file,
|
|
255
|
+
)
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Base class for all plugins."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
from reflex.constants.base import Dirs
|
|
7
|
+
from reflex.constants.compiler import Ext, PageNames
|
|
8
|
+
from reflex.plugins.base import Plugin as PluginBase
|
|
9
|
+
from reflex.utils.decorator import once
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Constants(SimpleNamespace):
|
|
13
|
+
"""Tailwind constants."""
|
|
14
|
+
|
|
15
|
+
# The Tailwindcss version
|
|
16
|
+
VERSION = "tailwindcss@4.1.7"
|
|
17
|
+
# The Tailwind config.
|
|
18
|
+
CONFIG = "tailwind.config.js"
|
|
19
|
+
# Default Tailwind content paths
|
|
20
|
+
CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"]
|
|
21
|
+
# Relative tailwind style path to root stylesheet in Dirs.STYLES.
|
|
22
|
+
ROOT_STYLE_PATH = "./tailwind.css"
|
|
23
|
+
|
|
24
|
+
# Content of the style content.
|
|
25
|
+
ROOT_STYLE_CONTENT = """@layer theme, base, components, utilities;
|
|
26
|
+
@config "../tailwind.config.js";
|
|
27
|
+
@import "tailwindcss/theme.css" layer(theme);
|
|
28
|
+
@import "tailwindcss/preflight.css" layer(base);
|
|
29
|
+
@import "{radix_url}" layer(components);
|
|
30
|
+
@import "tailwindcss/utilities.css" layer(utilities);
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# The default tailwind css.
|
|
34
|
+
TAILWIND_CSS = "@import url('./tailwind.css');"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@once
|
|
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
|
+
):
|
|
121
|
+
"""Compile the Tailwind config.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
config: The Tailwind config.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The compiled Tailwind config.
|
|
128
|
+
"""
|
|
129
|
+
return Constants.CONFIG, tailwind_config_js_template().render(
|
|
130
|
+
**config,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def compile_root_style():
|
|
135
|
+
"""Compile the Tailwind root style.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The compiled Tailwind root style.
|
|
139
|
+
"""
|
|
140
|
+
from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET
|
|
141
|
+
|
|
142
|
+
return str(
|
|
143
|
+
Path(Dirs.STYLES) / Constants.ROOT_STYLE_PATH
|
|
144
|
+
), Constants.ROOT_STYLE_CONTENT.format(
|
|
145
|
+
radix_url=RADIX_THEMES_STYLESHEET,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _index_of_element_that_has(haystack: list[str], needle: str) -> int | None:
|
|
150
|
+
return next(
|
|
151
|
+
(i for i, line in enumerate(haystack) if needle in line),
|
|
152
|
+
None,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def add_tailwind_to_postcss_config(postcss_file_content: str) -> str:
|
|
157
|
+
"""Add tailwind to the postcss config.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
postcss_file_content: The content of the postcss config file.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
The modified postcss config file content.
|
|
164
|
+
"""
|
|
165
|
+
from reflex.constants import Dirs
|
|
166
|
+
|
|
167
|
+
postcss_file_lines = postcss_file_content.splitlines()
|
|
168
|
+
|
|
169
|
+
line_with_postcss_plugins = _index_of_element_that_has(
|
|
170
|
+
postcss_file_lines, "plugins"
|
|
171
|
+
)
|
|
172
|
+
if not line_with_postcss_plugins:
|
|
173
|
+
print( # noqa: T201
|
|
174
|
+
f"Could not find line with 'plugins' in {Dirs.POSTCSS_JS}. "
|
|
175
|
+
"Please make sure the file exists and is valid."
|
|
176
|
+
)
|
|
177
|
+
return postcss_file_content
|
|
178
|
+
|
|
179
|
+
plugins_to_remove = ['"postcss-import"', "tailwindcss", "autoprefixer"]
|
|
180
|
+
plugins_to_add = ['"@tailwindcss/postcss"']
|
|
181
|
+
|
|
182
|
+
for plugin in plugins_to_remove:
|
|
183
|
+
plugin_index = _index_of_element_that_has(postcss_file_lines, plugin)
|
|
184
|
+
if plugin_index is not None:
|
|
185
|
+
postcss_file_lines.pop(plugin_index)
|
|
186
|
+
|
|
187
|
+
for plugin in plugins_to_add[::-1]:
|
|
188
|
+
if not _index_of_element_that_has(postcss_file_lines, plugin):
|
|
189
|
+
postcss_file_lines.insert(
|
|
190
|
+
line_with_postcss_plugins + 1, f" {plugin}: {{}},"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return "\n".join(postcss_file_lines)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def add_tailwind_to_css_file(css_file_content: str) -> str:
|
|
197
|
+
"""Add tailwind to the css file.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
css_file_content: The content of the css file.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The modified css file content.
|
|
204
|
+
"""
|
|
205
|
+
from reflex.compiler.compiler import RADIX_THEMES_STYLESHEET
|
|
206
|
+
|
|
207
|
+
if Constants.TAILWIND_CSS.splitlines()[0] in css_file_content:
|
|
208
|
+
return css_file_content
|
|
209
|
+
if RADIX_THEMES_STYLESHEET not in css_file_content:
|
|
210
|
+
print( # noqa: T201
|
|
211
|
+
f"Could not find line with '{RADIX_THEMES_STYLESHEET}' in {Dirs.STYLES}. "
|
|
212
|
+
"Please make sure the file exists and is valid."
|
|
213
|
+
)
|
|
214
|
+
return css_file_content
|
|
215
|
+
return css_file_content.replace(
|
|
216
|
+
f"@import url('{RADIX_THEMES_STYLESHEET}');",
|
|
217
|
+
Constants.TAILWIND_CSS,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class Plugin(PluginBase):
|
|
222
|
+
"""Plugin for Tailwind CSS."""
|
|
223
|
+
|
|
224
|
+
def get_frontend_development_dependencies(self, **context) -> list[str]:
|
|
225
|
+
"""Get the packages required by the plugin.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
**context: The context for the plugin.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
A list of packages required by the plugin.
|
|
232
|
+
"""
|
|
233
|
+
from reflex.config import get_config
|
|
234
|
+
|
|
235
|
+
config = get_config()
|
|
236
|
+
return [
|
|
237
|
+
plugin if isinstance(plugin, str) else plugin.get("name")
|
|
238
|
+
for plugin in (config.tailwind or {}).get("plugins", [])
|
|
239
|
+
] + [Constants.VERSION, "@tailwindcss/postcss@4.1.7"]
|
|
240
|
+
|
|
241
|
+
def pre_compile(self, **context):
|
|
242
|
+
"""Pre-compile the plugin.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
context: The context for the plugin.
|
|
246
|
+
"""
|
|
247
|
+
from reflex.config import get_config
|
|
248
|
+
|
|
249
|
+
config = get_config().tailwind or {}
|
|
250
|
+
|
|
251
|
+
config["content"] = config.get("content", Constants.CONTENT)
|
|
252
|
+
context["add_save_task"](compile_config, config)
|
|
253
|
+
context["add_save_task"](compile_root_style)
|
|
254
|
+
context["add_modify_task"](Dirs.POSTCSS_JS, add_tailwind_to_postcss_config)
|
|
255
|
+
context["add_modify_task"](
|
|
256
|
+
str(Path(Dirs.STYLES) / (PageNames.STYLESHEET_ROOT + Ext.CSS)),
|
|
257
|
+
add_tailwind_to_css_file,
|
|
258
|
+
)
|
reflex/state.py
CHANGED
|
@@ -1003,6 +1003,18 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1003
1003
|
raise ValueError(f"Invalid path: {path}")
|
|
1004
1004
|
return getattr(substate, name)
|
|
1005
1005
|
|
|
1006
|
+
@classmethod
|
|
1007
|
+
def is_user_defined(cls) -> bool:
|
|
1008
|
+
"""Check if the state is user-defined.
|
|
1009
|
+
|
|
1010
|
+
Returns:
|
|
1011
|
+
True if the state is user-defined, False otherwise.
|
|
1012
|
+
"""
|
|
1013
|
+
return (
|
|
1014
|
+
not cls.__module__.startswith("reflex.")
|
|
1015
|
+
or cls.__module__ == "reflex.istate.dynamic"
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1006
1018
|
@classmethod
|
|
1007
1019
|
def _init_var(cls, prop: Var):
|
|
1008
1020
|
"""Initialize a variable.
|
|
@@ -1024,7 +1036,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
|
|
|
1024
1036
|
f'Found var "{prop._js_expr}" with type {prop._var_type}.'
|
|
1025
1037
|
)
|
|
1026
1038
|
cls._set_var(prop)
|
|
1027
|
-
if get_config().state_auto_setters:
|
|
1039
|
+
if cls.is_user_defined() and get_config().state_auto_setters:
|
|
1028
1040
|
cls._create_setter(prop)
|
|
1029
1041
|
cls._set_default_value(prop)
|
|
1030
1042
|
|
|
@@ -2330,6 +2342,15 @@ class State(BaseState):
|
|
|
2330
2342
|
# The hydrated bool.
|
|
2331
2343
|
is_hydrated: bool = False
|
|
2332
2344
|
|
|
2345
|
+
@event
|
|
2346
|
+
def set_is_hydrated(self, value: bool) -> None:
|
|
2347
|
+
"""Set the hydrated state.
|
|
2348
|
+
|
|
2349
|
+
Args:
|
|
2350
|
+
value: The hydrated state.
|
|
2351
|
+
"""
|
|
2352
|
+
self.is_hydrated = value
|
|
2353
|
+
|
|
2333
2354
|
|
|
2334
2355
|
T = TypeVar("T", bound=BaseState)
|
|
2335
2356
|
|
|
@@ -2424,7 +2445,7 @@ class OnLoadInternalState(State):
|
|
|
2424
2445
|
This is a separate substate to avoid deserializing the entire state tree for every page navigation.
|
|
2425
2446
|
"""
|
|
2426
2447
|
|
|
2427
|
-
def on_load_internal(self) -> list[Event | EventSpec] | None:
|
|
2448
|
+
def on_load_internal(self) -> list[Event | EventSpec | event.EventCallback] | None:
|
|
2428
2449
|
"""Queue on_load handlers for the current page.
|
|
2429
2450
|
|
|
2430
2451
|
Returns:
|
|
@@ -2444,7 +2465,7 @@ class OnLoadInternalState(State):
|
|
|
2444
2465
|
self.router.session.client_token,
|
|
2445
2466
|
router_data=self.router_data,
|
|
2446
2467
|
),
|
|
2447
|
-
State.set_is_hydrated(True),
|
|
2468
|
+
State.set_is_hydrated(True),
|
|
2448
2469
|
]
|
|
2449
2470
|
|
|
2450
2471
|
|
reflex/utils/build.py
CHANGED
|
@@ -230,7 +230,7 @@ def setup_frontend(
|
|
|
230
230
|
"""
|
|
231
231
|
# Create the assets dir if it doesn't exist.
|
|
232
232
|
path_ops.mkdir(constants.Dirs.APP_ASSETS)
|
|
233
|
-
path_ops.
|
|
233
|
+
path_ops.copy_tree(
|
|
234
234
|
src=str(root / constants.Dirs.APP_ASSETS),
|
|
235
235
|
dest=str(root / prerequisites.get_web_dir() / constants.Dirs.PUBLIC),
|
|
236
236
|
ignore=tuple(f"*.{ext}" for ext in constants.Reflex.STYLESHEETS_SUPPORTED),
|
reflex/utils/console.py
CHANGED
|
@@ -65,7 +65,7 @@ def set_log_level(log_level: LogLevel | None):
|
|
|
65
65
|
global _LOG_LEVEL
|
|
66
66
|
if log_level != _LOG_LEVEL:
|
|
67
67
|
# Set the loglevel persistenly for subprocesses.
|
|
68
|
-
os.environ["
|
|
68
|
+
os.environ["REFLEX_LOGLEVEL"] = log_level.value
|
|
69
69
|
_LOG_LEVEL = log_level
|
|
70
70
|
|
|
71
71
|
|
reflex/utils/exec.py
CHANGED
|
@@ -374,6 +374,23 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
374
374
|
)
|
|
375
375
|
|
|
376
376
|
|
|
377
|
+
HOTRELOAD_IGNORE_EXTENSIONS = (
|
|
378
|
+
"txt",
|
|
379
|
+
"toml",
|
|
380
|
+
"sqlite",
|
|
381
|
+
"yaml",
|
|
382
|
+
"yml",
|
|
383
|
+
"json",
|
|
384
|
+
"sh",
|
|
385
|
+
"bash",
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
HOTRELOAD_IGNORE_PATTERNS = (
|
|
389
|
+
*[rf"^.*\.{ext}$" for ext in HOTRELOAD_IGNORE_EXTENSIONS],
|
|
390
|
+
r"^[^\.]*$", # Ignore files without an extension
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
377
394
|
def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
378
395
|
"""Run the backend in development mode using Granian.
|
|
379
396
|
|
|
@@ -384,6 +401,11 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
384
401
|
"""
|
|
385
402
|
console.debug("Using Granian for backend")
|
|
386
403
|
|
|
404
|
+
if environment.REFLEX_STRICT_HOT_RELOAD.get():
|
|
405
|
+
import multiprocessing
|
|
406
|
+
|
|
407
|
+
multiprocessing.set_start_method("spawn", force=True)
|
|
408
|
+
|
|
387
409
|
from granian.constants import Interfaces
|
|
388
410
|
from granian.log import LogLevels
|
|
389
411
|
from granian.server import MPServer as Granian
|
|
@@ -398,6 +420,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
|
|
|
398
420
|
reload=True,
|
|
399
421
|
reload_paths=get_reload_paths(),
|
|
400
422
|
reload_ignore_worker_failure=True,
|
|
423
|
+
reload_ignore_patterns=HOTRELOAD_IGNORE_PATTERNS,
|
|
401
424
|
reload_tick=100,
|
|
402
425
|
workers_kill_timeout=2,
|
|
403
426
|
).serve()
|
reflex/utils/path_ops.py
CHANGED
|
@@ -42,6 +42,31 @@ def rm(path: str | Path):
|
|
|
42
42
|
path.unlink()
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def copy_tree(
|
|
46
|
+
src: str | Path,
|
|
47
|
+
dest: str | Path,
|
|
48
|
+
ignore: tuple[str, ...] | None = None,
|
|
49
|
+
):
|
|
50
|
+
"""Copy a directory tree.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
src: The path to the source directory.
|
|
54
|
+
dest: The path to the destination directory.
|
|
55
|
+
ignore: Ignoring files and directories that match one of the glob-style patterns provided
|
|
56
|
+
"""
|
|
57
|
+
src = Path(src)
|
|
58
|
+
dest = Path(dest)
|
|
59
|
+
if dest.exists():
|
|
60
|
+
for item in dest.iterdir():
|
|
61
|
+
rm(item)
|
|
62
|
+
shutil.copytree(
|
|
63
|
+
src,
|
|
64
|
+
dest,
|
|
65
|
+
ignore=shutil.ignore_patterns(*ignore) if ignore is not None else ignore,
|
|
66
|
+
dirs_exist_ok=True,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
45
70
|
def cp(
|
|
46
71
|
src: str | Path,
|
|
47
72
|
dest: str | Path,
|
|
@@ -65,12 +90,7 @@ def cp(
|
|
|
65
90
|
if not overwrite and dest.exists():
|
|
66
91
|
return False
|
|
67
92
|
if src.is_dir():
|
|
68
|
-
|
|
69
|
-
shutil.copytree(
|
|
70
|
-
src,
|
|
71
|
-
dest,
|
|
72
|
-
ignore=shutil.ignore_patterns(*ignore) if ignore is not None else ignore,
|
|
73
|
-
)
|
|
93
|
+
copy_tree(src, dest, ignore)
|
|
74
94
|
else:
|
|
75
95
|
shutil.copyfile(src, dest)
|
|
76
96
|
return True
|