reflex 0.7.12a1__py3-none-any.whl → 0.7.13a1__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.

Files changed (50) hide show
  1. reflex/.templates/jinja/app/rxconfig.py.jinja2 +1 -0
  2. reflex/.templates/web/postcss.config.js +0 -1
  3. reflex/.templates/web/utils/state.js +1 -1
  4. reflex/__init__.py +1 -0
  5. reflex/__init__.pyi +1 -0
  6. reflex/app.py +87 -27
  7. reflex/compiler/compiler.py +11 -62
  8. reflex/compiler/templates.py +12 -3
  9. reflex/compiler/utils.py +20 -4
  10. reflex/components/component.py +366 -88
  11. reflex/components/datadisplay/code.py +1 -1
  12. reflex/components/datadisplay/shiki_code_block.py +97 -86
  13. reflex/components/datadisplay/shiki_code_block.pyi +4 -2
  14. reflex/components/el/elements/forms.py +1 -1
  15. reflex/components/lucide/icon.py +2 -1
  16. reflex/components/lucide/icon.pyi +1 -0
  17. reflex/components/plotly/plotly.py +2 -2
  18. reflex/components/plotly/plotly.pyi +2 -3
  19. reflex/components/radix/themes/base.py +4 -11
  20. reflex/components/radix/themes/components/icon_button.py +2 -2
  21. reflex/components/radix/themes/components/text_field.py +3 -0
  22. reflex/components/radix/themes/components/text_field.pyi +2 -0
  23. reflex/components/radix/themes/layout/list.py +1 -1
  24. reflex/components/tags/iter_tag.py +3 -5
  25. reflex/config.py +57 -7
  26. reflex/constants/__init__.py +0 -2
  27. reflex/event.py +154 -93
  28. reflex/plugins/__init__.py +7 -0
  29. reflex/plugins/base.py +101 -0
  30. reflex/plugins/tailwind_v3.py +255 -0
  31. reflex/plugins/tailwind_v4.py +257 -0
  32. reflex/state.py +24 -3
  33. reflex/utils/build.py +1 -1
  34. reflex/utils/console.py +1 -1
  35. reflex/utils/exec.py +23 -0
  36. reflex/utils/path_ops.py +26 -6
  37. reflex/utils/prerequisites.py +21 -90
  38. reflex/utils/pyi_generator.py +12 -2
  39. reflex/utils/types.py +15 -1
  40. reflex/vars/base.py +59 -4
  41. reflex/vars/object.py +8 -0
  42. {reflex-0.7.12a1.dist-info → reflex-0.7.13a1.dist-info}/METADATA +2 -2
  43. {reflex-0.7.12a1.dist-info → reflex-0.7.13a1.dist-info}/RECORD +47 -46
  44. scripts/hatch_build.py +17 -0
  45. reflex/.templates/jinja/web/tailwind.config.js.jinja2 +0 -66
  46. reflex/.templates/web/styles/tailwind.css +0 -6
  47. reflex/constants/style.py +0 -16
  48. {reflex-0.7.12a1.dist-info → reflex-0.7.13a1.dist-info}/WHEEL +0 -0
  49. {reflex-0.7.12a1.dist-info → reflex-0.7.13a1.dist-info}/entry_points.txt +0 -0
  50. {reflex-0.7.12a1.dist-info → reflex-0.7.13a1.dist-info}/licenses/LICENSE +0 -0
reflex/event.py CHANGED
@@ -1262,6 +1262,118 @@ def get_hydrate_event(state: BaseState) -> str:
1262
1262
  return get_event(state, constants.CompileVars.HYDRATE)
1263
1263
 
1264
1264
 
1265
+ def _values_returned_from_event(
1266
+ event_spec: ArgsSpec | Sequence[ArgsSpec],
1267
+ ) -> list[Any]:
1268
+ return [
1269
+ event_spec_return_type
1270
+ for arg_spec in (
1271
+ [event_spec] if not isinstance(event_spec, Sequence) else list(event_spec)
1272
+ )
1273
+ if (event_spec_return_type := get_type_hints(arg_spec).get("return", None))
1274
+ is not None
1275
+ and get_origin(event_spec_return_type) is tuple
1276
+ ]
1277
+
1278
+
1279
+ def _check_event_args_subclass_of_callback(
1280
+ callback_params_names: list[str],
1281
+ provided_event_types: list[Any],
1282
+ callback_param_name_to_type: dict[str, Any],
1283
+ callback_name: str = "",
1284
+ key: str = "",
1285
+ ):
1286
+ """Check if the event handler arguments are subclass of the callback.
1287
+
1288
+ Args:
1289
+ callback_params_names: The names of the callback parameters.
1290
+ provided_event_types: The event types.
1291
+ callback_param_name_to_type: The callback parameter name to type mapping.
1292
+ callback_name: The name of the callback.
1293
+ key: The key.
1294
+
1295
+ Raises:
1296
+ TypeError: If the event handler arguments are invalid.
1297
+ EventHandlerArgTypeMismatchError: If the event handler arguments do not match the callback.
1298
+
1299
+ # noqa: DAR401 delayed_exceptions[]
1300
+ # noqa: DAR402 EventHandlerArgTypeMismatchError
1301
+ """
1302
+ type_match_found: dict[str, bool] = {}
1303
+ delayed_exceptions: list[EventHandlerArgTypeMismatchError] = []
1304
+
1305
+ for event_spec_index, event_spec_return_type in enumerate(provided_event_types):
1306
+ args = get_args(event_spec_return_type)
1307
+
1308
+ args_types_without_vars = [
1309
+ arg if get_origin(arg) is not Var else get_args(arg)[0] for arg in args
1310
+ ]
1311
+
1312
+ # check that args of event handler are matching the spec if type hints are provided
1313
+ for i, arg in enumerate(callback_params_names[: len(args_types_without_vars)]):
1314
+ if arg not in callback_param_name_to_type:
1315
+ continue
1316
+
1317
+ type_match_found.setdefault(arg, False)
1318
+
1319
+ try:
1320
+ compare_result = typehint_issubclass(
1321
+ args_types_without_vars[i], callback_param_name_to_type[arg]
1322
+ )
1323
+ except TypeError as te:
1324
+ callback_name_context = f" of {callback_name}" if callback_name else ""
1325
+ key_context = f" for {key}" if key else ""
1326
+ raise TypeError(
1327
+ f"Could not compare types {args_types_without_vars[i]} and {callback_param_name_to_type[arg]} for argument {arg}{callback_name_context}{key_context}."
1328
+ ) from te
1329
+
1330
+ if compare_result:
1331
+ type_match_found[arg] = True
1332
+ continue
1333
+ else:
1334
+ type_match_found[arg] = False
1335
+ as_annotated_in = (
1336
+ f" as annotated in {callback_name}" if callback_name else ""
1337
+ )
1338
+ delayed_exceptions.append(
1339
+ EventHandlerArgTypeMismatchError(
1340
+ f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {callback_param_name_to_type[arg]}{as_annotated_in} instead."
1341
+ )
1342
+ )
1343
+
1344
+ if all(type_match_found.values()):
1345
+ delayed_exceptions.clear()
1346
+ if event_spec_index:
1347
+ args = get_args(provided_event_types[0])
1348
+
1349
+ args_types_without_vars = [
1350
+ arg if get_origin(arg) is not Var else get_args(arg)[0]
1351
+ for arg in args
1352
+ ]
1353
+
1354
+ expect_string = ", ".join(
1355
+ repr(arg) for arg in args_types_without_vars
1356
+ ).replace("[", "\\[")
1357
+
1358
+ given_string = ", ".join(
1359
+ repr(callback_param_name_to_type.get(arg, Any))
1360
+ for arg in callback_params_names
1361
+ ).replace("[", "\\[")
1362
+
1363
+ as_annotated_in = (
1364
+ f" as annotated in {callback_name}" if callback_name else ""
1365
+ )
1366
+
1367
+ console.warn(
1368
+ f"Event handler {key} expects ({expect_string}) -> () but got ({given_string}) -> (){as_annotated_in} instead. "
1369
+ f"This may lead to unexpected behavior but is intentionally ignored for {key}."
1370
+ )
1371
+ break
1372
+
1373
+ if delayed_exceptions:
1374
+ raise delayed_exceptions[0]
1375
+
1376
+
1265
1377
  def call_event_handler(
1266
1378
  event_callback: EventHandler | EventSpec,
1267
1379
  event_spec: ArgsSpec | Sequence[ArgsSpec],
@@ -1278,17 +1390,13 @@ def call_event_handler(
1278
1390
  event_spec: The lambda that define the argument(s) to pass to the event handler.
1279
1391
  key: The key to pass to the event handler.
1280
1392
 
1281
- Raises:
1282
- EventHandlerArgTypeMismatchError: If the event handler arguments do not match the event spec. #noqa: DAR402
1283
- TypeError: If the event handler arguments are invalid.
1284
-
1285
1393
  Returns:
1286
1394
  The event spec from calling the event handler.
1287
-
1288
- #noqa: DAR401
1289
1395
  """
1290
1396
  event_spec_args = parse_args_spec(event_spec)
1291
1397
 
1398
+ event_spec_return_types = _values_returned_from_event(event_spec)
1399
+
1292
1400
  if isinstance(event_callback, EventSpec):
1293
1401
  check_fn_match_arg_spec(
1294
1402
  event_callback.handler.fn,
@@ -1297,6 +1405,32 @@ def call_event_handler(
1297
1405
  bool(event_callback.handler.state_full_name) + len(event_callback.args),
1298
1406
  event_callback.handler.fn.__qualname__,
1299
1407
  )
1408
+
1409
+ event_callback_spec_args = list(
1410
+ inspect.signature(event_callback.handler.fn).parameters.keys()
1411
+ )
1412
+
1413
+ try:
1414
+ type_hints_of_provided_callback = get_type_hints(event_callback.handler.fn)
1415
+ except NameError:
1416
+ type_hints_of_provided_callback = {}
1417
+
1418
+ argument_names = [str(arg) for arg, value in event_callback.args]
1419
+
1420
+ _check_event_args_subclass_of_callback(
1421
+ [
1422
+ arg
1423
+ for arg in event_callback_spec_args[
1424
+ bool(event_callback.handler.state_full_name) :
1425
+ ]
1426
+ if arg not in argument_names
1427
+ ],
1428
+ event_spec_return_types,
1429
+ type_hints_of_provided_callback,
1430
+ event_callback.handler.fn.__qualname__,
1431
+ key or "",
1432
+ )
1433
+
1300
1434
  # Handle partial application of EventSpec args
1301
1435
  return event_callback.add_args(*event_spec_args)
1302
1436
 
@@ -1308,98 +1442,23 @@ def call_event_handler(
1308
1442
  event_callback.fn.__qualname__,
1309
1443
  )
1310
1444
 
1311
- all_acceptable_specs = (
1312
- [event_spec] if not isinstance(event_spec, Sequence) else event_spec
1313
- )
1314
-
1315
- event_spec_return_types = list(
1316
- filter(
1317
- lambda event_spec_return_type: event_spec_return_type is not None
1318
- and get_origin(event_spec_return_type) is tuple,
1319
- (
1320
- get_type_hints(arg_spec).get("return", None)
1321
- for arg_spec in all_acceptable_specs
1322
- ),
1323
- )
1324
- )
1325
- type_match_found: dict[str, bool] = {}
1326
- delayed_exceptions: list[EventHandlerArgTypeMismatchError] = []
1327
-
1328
- try:
1329
- type_hints_of_provided_callback = get_type_hints(event_callback.fn)
1330
- except NameError:
1331
- type_hints_of_provided_callback = {}
1332
-
1333
1445
  if event_spec_return_types:
1334
1446
  event_callback_spec_args = list(
1335
1447
  inspect.signature(event_callback.fn).parameters.keys()
1336
1448
  )
1337
1449
 
1338
- for event_spec_index, event_spec_return_type in enumerate(
1339
- event_spec_return_types
1340
- ):
1341
- args = get_args(event_spec_return_type)
1342
-
1343
- args_types_without_vars = [
1344
- arg if get_origin(arg) is not Var else get_args(arg)[0] for arg in args
1345
- ]
1346
-
1347
- # check that args of event handler are matching the spec if type hints are provided
1348
- for i, arg in enumerate(
1349
- event_callback_spec_args[1 : len(args_types_without_vars) + 1]
1350
- ):
1351
- if arg not in type_hints_of_provided_callback:
1352
- continue
1353
-
1354
- type_match_found.setdefault(arg, False)
1355
-
1356
- try:
1357
- compare_result = typehint_issubclass(
1358
- args_types_without_vars[i], type_hints_of_provided_callback[arg]
1359
- )
1360
- except TypeError as te:
1361
- raise TypeError(
1362
- f"Could not compare types {args_types_without_vars[i]} and {type_hints_of_provided_callback[arg]} for argument {arg} of {event_callback.fn.__qualname__} provided for {key}."
1363
- ) from te
1364
-
1365
- if compare_result:
1366
- type_match_found[arg] = True
1367
- continue
1368
- else:
1369
- type_match_found[arg] = False
1370
- delayed_exceptions.append(
1371
- EventHandlerArgTypeMismatchError(
1372
- f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {type_hints_of_provided_callback[arg]} as annotated in {event_callback.fn.__qualname__} instead."
1373
- )
1374
- )
1375
-
1376
- if all(type_match_found.values()):
1377
- delayed_exceptions.clear()
1378
- if event_spec_index:
1379
- args = get_args(event_spec_return_types[0])
1380
-
1381
- args_types_without_vars = [
1382
- arg if get_origin(arg) is not Var else get_args(arg)[0]
1383
- for arg in args
1384
- ]
1385
-
1386
- expect_string = ", ".join(
1387
- repr(arg) for arg in args_types_without_vars
1388
- ).replace("[", "\\[")
1389
-
1390
- given_string = ", ".join(
1391
- repr(type_hints_of_provided_callback.get(arg, Any))
1392
- for arg in event_callback_spec_args[1:]
1393
- ).replace("[", "\\[")
1394
-
1395
- console.warn(
1396
- f"Event handler {key} expects ({expect_string}) -> () but got ({given_string}) -> () as annotated in {event_callback.fn.__qualname__} instead. "
1397
- f"This may lead to unexpected behavior but is intentionally ignored for {key}."
1398
- )
1399
- break
1400
-
1401
- if delayed_exceptions:
1402
- raise delayed_exceptions[0]
1450
+ try:
1451
+ type_hints_of_provided_callback = get_type_hints(event_callback.fn)
1452
+ except NameError:
1453
+ type_hints_of_provided_callback = {}
1454
+
1455
+ _check_event_args_subclass_of_callback(
1456
+ event_callback_spec_args[1:],
1457
+ event_spec_return_types,
1458
+ type_hints_of_provided_callback,
1459
+ event_callback.fn.__qualname__,
1460
+ key or "",
1461
+ )
1403
1462
 
1404
1463
  return event_callback(*event_spec_args)
1405
1464
 
@@ -1958,6 +2017,8 @@ class EventCallback(Generic[Unpack[P]], EventActionsMixin):
1958
2017
  class LambdaEventCallback(Protocol[Unpack[P]]):
1959
2018
  """A protocol for a lambda event callback."""
1960
2019
 
2020
+ __code__: types.CodeType
2021
+
1961
2022
  @overload
1962
2023
  def __call__(self: LambdaEventCallback[()]) -> Any: ...
1963
2024
 
@@ -0,0 +1,7 @@
1
+ """Reflex Plugin System."""
2
+
3
+ from .base import CommonContext as CommonContext
4
+ from .base import Plugin as Plugin
5
+ from .base import PreCompileContext as PreCompileContext
6
+ from .tailwind_v3 import Plugin as TailwindV3Plugin
7
+ from .tailwind_v4 import Plugin as TailwindV4Plugin
reflex/plugins/base.py ADDED
@@ -0,0 +1,101 @@
1
+ """Base class for all plugins."""
2
+
3
+ from collections.abc import Callable, Sequence
4
+ from pathlib import Path
5
+ from typing import ParamSpec, Protocol, TypedDict
6
+
7
+ from typing_extensions import Unpack
8
+
9
+
10
+ class CommonContext(TypedDict):
11
+ """Common context for all plugins."""
12
+
13
+
14
+ P = ParamSpec("P")
15
+
16
+
17
+ class AddTaskProtcol(Protocol):
18
+ """Protocol for adding a task to the pre-compile context."""
19
+
20
+ def __call__(
21
+ self,
22
+ task: Callable[P, list[tuple[str, str]] | tuple[str, str] | None],
23
+ /,
24
+ *args: P.args,
25
+ **kwargs: P.kwargs,
26
+ ) -> None:
27
+ """Add a task to the pre-compile context.
28
+
29
+ Args:
30
+ task: The task to add.
31
+ args: The arguments to pass to the task
32
+ kwargs: The keyword arguments to pass to the task
33
+ """
34
+
35
+
36
+ class PreCompileContext(CommonContext):
37
+ """Context for pre-compile hooks."""
38
+
39
+ add_save_task: AddTaskProtcol
40
+ add_modify_task: Callable[[str, Callable[[str], str]], None]
41
+
42
+
43
+ class Plugin:
44
+ """Base class for all plugins."""
45
+
46
+ def get_frontend_development_dependencies(
47
+ self, **context: Unpack[CommonContext]
48
+ ) -> list[str] | set[str] | tuple[str, ...]:
49
+ """Get the NPM packages required by the plugin for development.
50
+
51
+ Args:
52
+ context: The context for the plugin.
53
+
54
+ Returns:
55
+ A list of packages required by the plugin for development.
56
+ """
57
+ return []
58
+
59
+ def get_frontend_dependencies(
60
+ self, **context: Unpack[CommonContext]
61
+ ) -> list[str] | set[str] | tuple[str, ...]:
62
+ """Get the NPM packages required by the plugin.
63
+
64
+ Args:
65
+ context: The context for the plugin.
66
+
67
+ Returns:
68
+ A list of packages required by the plugin.
69
+ """
70
+ return []
71
+
72
+ def get_static_assets(
73
+ self, **context: Unpack[CommonContext]
74
+ ) -> Sequence[tuple[Path, str | bytes]]:
75
+ """Get the static assets required by the plugin.
76
+
77
+ Args:
78
+ context: The context for the plugin.
79
+
80
+ Returns:
81
+ A list of static assets required by the plugin.
82
+ """
83
+ return []
84
+
85
+ def get_stylesheet_paths(self, **context: Unpack[CommonContext]) -> Sequence[str]:
86
+ """Get the paths to the stylesheets required by the plugin relative to the styles directory.
87
+
88
+ Args:
89
+ context: The context for the plugin.
90
+
91
+ Returns:
92
+ A list of paths to the stylesheets required by the plugin.
93
+ """
94
+ return []
95
+
96
+ def pre_compile(self, **context: Unpack[PreCompileContext]) -> None:
97
+ """Called before the compilation of the plugin.
98
+
99
+ Args:
100
+ context: The context for the plugin.
101
+ """
@@ -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
+ )