reflex 0.6.3.post1__py3-none-any.whl → 0.6.4a1__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 (96) hide show
  1. reflex/.templates/jinja/web/pages/_app.js.jinja2 +2 -2
  2. reflex/.templates/jinja/web/utils/context.js.jinja2 +3 -1
  3. reflex/.templates/web/components/reflex/radix_themes_color_mode_provider.js +3 -3
  4. reflex/.templates/web/components/shiki/code.js +29 -0
  5. reflex/.templates/web/jsconfig.json +2 -1
  6. reflex/.templates/web/utils/state.js +6 -4
  7. reflex/__init__.py +6 -3
  8. reflex/__init__.pyi +4 -3
  9. reflex/app.py +6 -5
  10. reflex/compiler/compiler.py +6 -7
  11. reflex/compiler/utils.py +8 -1
  12. reflex/components/base/error_boundary.py +37 -24
  13. reflex/components/base/error_boundary.pyi +8 -7
  14. reflex/components/component.py +8 -3
  15. reflex/components/core/banner.py +2 -2
  16. reflex/components/core/client_side_routing.py +1 -1
  17. reflex/components/core/clipboard.py +1 -1
  18. reflex/components/core/clipboard.pyi +1 -1
  19. reflex/components/core/cond.py +1 -1
  20. reflex/components/core/debounce.py +5 -1
  21. reflex/components/core/upload.py +7 -9
  22. reflex/components/core/upload.pyi +2 -2
  23. reflex/components/datadisplay/code.py +1 -1
  24. reflex/components/datadisplay/dataeditor.py +83 -18
  25. reflex/components/datadisplay/dataeditor.pyi +67 -15
  26. reflex/components/datadisplay/shiki_code_block.py +813 -0
  27. reflex/components/datadisplay/shiki_code_block.pyi +2211 -0
  28. reflex/components/dynamic.py +3 -3
  29. reflex/components/el/elements/forms.py +37 -23
  30. reflex/components/el/elements/forms.pyi +7 -4
  31. reflex/components/markdown/markdown.py +12 -1
  32. reflex/components/moment/moment.pyi +1 -1
  33. reflex/components/radix/primitives/drawer.pyi +2 -2
  34. reflex/components/radix/themes/base.py +2 -2
  35. reflex/components/radix/themes/color_mode.pyi +1 -1
  36. reflex/components/radix/themes/components/alert_dialog.pyi +1 -1
  37. reflex/components/radix/themes/components/checkbox.pyi +3 -3
  38. reflex/components/radix/themes/components/context_menu.pyi +1 -1
  39. reflex/components/radix/themes/components/dialog.pyi +2 -2
  40. reflex/components/radix/themes/components/dropdown_menu.pyi +2 -2
  41. reflex/components/radix/themes/components/hover_card.pyi +2 -2
  42. reflex/components/radix/themes/components/popover.pyi +1 -1
  43. reflex/components/radix/themes/components/radio_cards.pyi +1 -1
  44. reflex/components/radix/themes/components/radio_group.pyi +1 -1
  45. reflex/components/radix/themes/components/select.pyi +6 -6
  46. reflex/components/radix/themes/components/switch.pyi +1 -1
  47. reflex/components/radix/themes/components/tabs.pyi +2 -2
  48. reflex/components/radix/themes/components/tooltip.pyi +4 -2
  49. reflex/components/react_player/__init__.py +1 -0
  50. reflex/components/react_player/audio.pyi +6 -3
  51. reflex/components/react_player/react_player.py +12 -1
  52. reflex/components/react_player/react_player.pyi +11 -3
  53. reflex/components/react_player/video.pyi +6 -3
  54. reflex/components/recharts/recharts.py +2 -2
  55. reflex/components/sonner/toast.py +1 -1
  56. reflex/components/suneditor/editor.py +40 -16
  57. reflex/components/suneditor/editor.pyi +15 -11
  58. reflex/config.py +236 -20
  59. reflex/constants/__init__.py +2 -0
  60. reflex/constants/base.py +53 -31
  61. reflex/constants/compiler.py +2 -12
  62. reflex/constants/config.py +1 -2
  63. reflex/constants/installer.py +88 -32
  64. reflex/constants/style.py +1 -1
  65. reflex/constants/utils.py +32 -0
  66. reflex/custom_components/custom_components.py +3 -3
  67. reflex/event.py +147 -84
  68. reflex/experimental/__init__.py +2 -0
  69. reflex/experimental/client_state.py +1 -1
  70. reflex/experimental/layout.pyi +1 -1
  71. reflex/istate/storage.py +144 -0
  72. reflex/model.py +8 -11
  73. reflex/reflex.py +18 -17
  74. reflex/state.py +83 -149
  75. reflex/style.py +1 -1
  76. reflex/testing.py +2 -1
  77. reflex/utils/build.py +0 -12
  78. reflex/utils/exceptions.py +8 -0
  79. reflex/utils/exec.py +22 -4
  80. reflex/utils/imports.py +6 -0
  81. reflex/utils/net.py +2 -4
  82. reflex/utils/path_ops.py +7 -21
  83. reflex/utils/prerequisites.py +11 -17
  84. reflex/utils/pyi_generator.py +91 -3
  85. reflex/utils/registry.py +2 -6
  86. reflex/utils/types.py +14 -0
  87. reflex/vars/base.py +453 -424
  88. reflex/vars/function.py +6 -16
  89. reflex/vars/number.py +46 -67
  90. reflex/vars/object.py +1 -31
  91. reflex/vars/sequence.py +177 -47
  92. {reflex-0.6.3.post1.dist-info → reflex-0.6.4a1.dist-info}/METADATA +1 -1
  93. {reflex-0.6.3.post1.dist-info → reflex-0.6.4a1.dist-info}/RECORD +96 -91
  94. {reflex-0.6.3.post1.dist-info → reflex-0.6.4a1.dist-info}/LICENSE +0 -0
  95. {reflex-0.6.3.post1.dist-info → reflex-0.6.4a1.dist-info}/WHEEL +0 -0
  96. {reflex-0.6.3.post1.dist-info → reflex-0.6.4a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,813 @@
1
+ """Shiki syntax hghlighter component."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections import defaultdict
7
+ from typing import Any, Literal, Optional, Union
8
+
9
+ from reflex.base import Base
10
+ from reflex.components.component import Component, ComponentNamespace
11
+ from reflex.components.core.colors import color
12
+ from reflex.components.core.cond import color_mode_cond
13
+ from reflex.components.el.elements.forms import Button
14
+ from reflex.components.lucide.icon import Icon
15
+ from reflex.components.radix.themes.layout.box import Box
16
+ from reflex.event import call_script, set_clipboard
17
+ from reflex.style import Style
18
+ from reflex.utils.exceptions import VarTypeError
19
+ from reflex.utils.imports import ImportVar
20
+ from reflex.vars.base import LiteralVar, Var
21
+ from reflex.vars.function import FunctionStringVar
22
+ from reflex.vars.sequence import StringVar, string_replace_operation
23
+
24
+
25
+ def copy_script() -> Any:
26
+ """Copy script for the code block and modify the child SVG element.
27
+
28
+
29
+ Returns:
30
+ Any: The result of calling the script.
31
+ """
32
+ return call_script(
33
+ f"""
34
+ // Event listener for the parent click
35
+ document.addEventListener('click', function(event) {{
36
+ // Find the closest div (parent element)
37
+ const parent = event.target.closest('div');
38
+ // If the parent is found
39
+ if (parent) {{
40
+ // Find the SVG element within the parent
41
+ const svgIcon = parent.querySelector('svg');
42
+ // If the SVG exists, proceed with the script
43
+ if (svgIcon) {{
44
+ const originalPath = svgIcon.innerHTML;
45
+ const checkmarkPath = '<polyline points="20 6 9 17 4 12"></polyline>'; // Checkmark SVG path
46
+ function transition(element, scale, opacity) {{
47
+ element.style.transform = `scale(${{scale}})`;
48
+ element.style.opacity = opacity;
49
+ }}
50
+ // Animate the SVG
51
+ transition(svgIcon, 0, '0');
52
+ setTimeout(() => {{
53
+ svgIcon.innerHTML = checkmarkPath; // Replace content with checkmark
54
+ svgIcon.setAttribute('viewBox', '0 0 24 24'); // Adjust viewBox if necessary
55
+ transition(svgIcon, 1, '1');
56
+ setTimeout(() => {{
57
+ transition(svgIcon, 0, '0');
58
+ setTimeout(() => {{
59
+ svgIcon.innerHTML = originalPath; // Restore original SVG content
60
+ transition(svgIcon, 1, '1');
61
+ }}, 125);
62
+ }}, 600);
63
+ }}, 125);
64
+ }} else {{
65
+ // console.error('SVG element not found within the parent.');
66
+ }}
67
+ }} else {{
68
+ // console.error('Parent element not found.');
69
+ }}
70
+ }});
71
+ """
72
+ )
73
+
74
+
75
+ SHIKIJS_TRANSFORMER_FNS = {
76
+ "transformerNotationDiff",
77
+ "transformerNotationHighlight",
78
+ "transformerNotationWordHighlight",
79
+ "transformerNotationFocus",
80
+ "transformerNotationErrorLevel",
81
+ "transformerRenderWhitespace",
82
+ "transformerMetaHighlight",
83
+ "transformerMetaWordHighlight",
84
+ "transformerCompactLineOptions",
85
+ # TODO: this transformer when included adds a weird behavior which removes other code lines. Need to figure out why.
86
+ # "transformerRemoveLineBreak",
87
+ "transformerRemoveNotationEscape",
88
+ }
89
+ LINE_NUMBER_STYLING = {
90
+ "code": {
91
+ "counter-reset": "step",
92
+ "counter-increment": "step 0",
93
+ "display": "grid",
94
+ "line-height": "1.7",
95
+ "font-size": "0.875em",
96
+ },
97
+ "code .line::before": {
98
+ "content": "counter(step)",
99
+ "counter-increment": "step",
100
+ "width": "1rem",
101
+ "margin-right": "1.5rem",
102
+ "display": "inline-block",
103
+ "text-align": "right",
104
+ "color": "rgba(115,138,148,.4)",
105
+ },
106
+ }
107
+ BOX_PARENT_STYLING = {
108
+ "pre": {
109
+ "margin": "0",
110
+ "padding": "24px",
111
+ "background": "transparent",
112
+ "overflow-x": "auto",
113
+ "border-radius": "6px",
114
+ },
115
+ }
116
+
117
+ THEME_MAPPING = {
118
+ "light": "one-light",
119
+ "dark": "one-dark-pro",
120
+ "a11y-dark": "github-dark",
121
+ }
122
+ LANGUAGE_MAPPING = {"bash": "shellscript"}
123
+ LiteralCodeLanguage = Literal[
124
+ "abap",
125
+ "actionscript-3",
126
+ "ada",
127
+ "angular-html",
128
+ "angular-ts",
129
+ "apache",
130
+ "apex",
131
+ "apl",
132
+ "applescript",
133
+ "ara",
134
+ "asciidoc",
135
+ "asm",
136
+ "astro",
137
+ "awk",
138
+ "ballerina",
139
+ "bat",
140
+ "beancount",
141
+ "berry",
142
+ "bibtex",
143
+ "bicep",
144
+ "blade",
145
+ "c",
146
+ "cadence",
147
+ "clarity",
148
+ "clojure",
149
+ "cmake",
150
+ "cobol",
151
+ "codeowners",
152
+ "codeql",
153
+ "coffee",
154
+ "common-lisp",
155
+ "coq",
156
+ "cpp",
157
+ "crystal",
158
+ "csharp",
159
+ "css",
160
+ "csv",
161
+ "cue",
162
+ "cypher",
163
+ "d",
164
+ "dart",
165
+ "dax",
166
+ "desktop",
167
+ "diff",
168
+ "docker",
169
+ "dotenv",
170
+ "dream-maker",
171
+ "edge",
172
+ "elixir",
173
+ "elm",
174
+ "emacs-lisp",
175
+ "erb",
176
+ "erlang",
177
+ "fennel",
178
+ "fish",
179
+ "fluent",
180
+ "fortran-fixed-form",
181
+ "fortran-free-form",
182
+ "fsharp",
183
+ "gdresource",
184
+ "gdscript",
185
+ "gdshader",
186
+ "genie",
187
+ "gherkin",
188
+ "git-commit",
189
+ "git-rebase",
190
+ "gleam",
191
+ "glimmer-js",
192
+ "glimmer-ts",
193
+ "glsl",
194
+ "gnuplot",
195
+ "go",
196
+ "graphql",
197
+ "groovy",
198
+ "hack",
199
+ "haml",
200
+ "handlebars",
201
+ "haskell",
202
+ "haxe",
203
+ "hcl",
204
+ "hjson",
205
+ "hlsl",
206
+ "html",
207
+ "html-derivative",
208
+ "http",
209
+ "hxml",
210
+ "hy",
211
+ "imba",
212
+ "ini",
213
+ "java",
214
+ "javascript",
215
+ "jinja",
216
+ "jison",
217
+ "json",
218
+ "json5",
219
+ "jsonc",
220
+ "jsonl",
221
+ "jsonnet",
222
+ "jssm",
223
+ "jsx",
224
+ "julia",
225
+ "kotlin",
226
+ "kusto",
227
+ "latex",
228
+ "lean",
229
+ "less",
230
+ "liquid",
231
+ "log",
232
+ "logo",
233
+ "lua",
234
+ "luau",
235
+ "make",
236
+ "markdown",
237
+ "marko",
238
+ "matlab",
239
+ "mdc",
240
+ "mdx",
241
+ "mermaid",
242
+ "mojo",
243
+ "move",
244
+ "narrat",
245
+ "nextflow",
246
+ "nginx",
247
+ "nim",
248
+ "nix",
249
+ "nushell",
250
+ "objective-c",
251
+ "objective-cpp",
252
+ "ocaml",
253
+ "pascal",
254
+ "perl",
255
+ "php",
256
+ "plsql",
257
+ "po",
258
+ "postcss",
259
+ "powerquery",
260
+ "powershell",
261
+ "prisma",
262
+ "prolog",
263
+ "proto",
264
+ "pug",
265
+ "puppet",
266
+ "purescript",
267
+ "python",
268
+ "qml",
269
+ "qmldir",
270
+ "qss",
271
+ "r",
272
+ "racket",
273
+ "raku",
274
+ "razor",
275
+ "reg",
276
+ "regexp",
277
+ "rel",
278
+ "riscv",
279
+ "rst",
280
+ "ruby",
281
+ "rust",
282
+ "sas",
283
+ "sass",
284
+ "scala",
285
+ "scheme",
286
+ "scss",
287
+ "shaderlab",
288
+ "shellscript",
289
+ "shellsession",
290
+ "smalltalk",
291
+ "solidity",
292
+ "soy",
293
+ "sparql",
294
+ "splunk",
295
+ "sql",
296
+ "ssh-config",
297
+ "stata",
298
+ "stylus",
299
+ "svelte",
300
+ "swift",
301
+ "system-verilog",
302
+ "systemd",
303
+ "tasl",
304
+ "tcl",
305
+ "templ",
306
+ "terraform",
307
+ "tex",
308
+ "toml",
309
+ "ts-tags",
310
+ "tsv",
311
+ "tsx",
312
+ "turtle",
313
+ "twig",
314
+ "typescript",
315
+ "typespec",
316
+ "typst",
317
+ "v",
318
+ "vala",
319
+ "vb",
320
+ "verilog",
321
+ "vhdl",
322
+ "viml",
323
+ "vue",
324
+ "vue-html",
325
+ "vyper",
326
+ "wasm",
327
+ "wenyan",
328
+ "wgsl",
329
+ "wikitext",
330
+ "wolfram",
331
+ "xml",
332
+ "xsl",
333
+ "yaml",
334
+ "zenscript",
335
+ "zig",
336
+ ]
337
+ LiteralCodeTheme = Literal[
338
+ "andromeeda",
339
+ "aurora-x",
340
+ "ayu-dark",
341
+ "catppuccin-frappe",
342
+ "catppuccin-latte",
343
+ "catppuccin-macchiato",
344
+ "catppuccin-mocha",
345
+ "dark-plus",
346
+ "dracula",
347
+ "dracula-soft",
348
+ "everforest-dark",
349
+ "everforest-light",
350
+ "github-dark",
351
+ "github-dark-default",
352
+ "github-dark-dimmed",
353
+ "github-dark-high-contrast",
354
+ "github-light",
355
+ "github-light-default",
356
+ "github-light-high-contrast",
357
+ "houston",
358
+ "laserwave",
359
+ "light-plus",
360
+ "material-theme",
361
+ "material-theme-darker",
362
+ "material-theme-lighter",
363
+ "material-theme-ocean",
364
+ "material-theme-palenight",
365
+ "min-dark",
366
+ "min-light",
367
+ "monokai",
368
+ "night-owl",
369
+ "nord",
370
+ "one-dark-pro",
371
+ "one-light",
372
+ "plain",
373
+ "plastic",
374
+ "poimandres",
375
+ "red",
376
+ "rose-pine",
377
+ "rose-pine-dawn",
378
+ "rose-pine-moon",
379
+ "slack-dark",
380
+ "slack-ochin",
381
+ "snazzy-light",
382
+ "solarized-dark",
383
+ "solarized-light",
384
+ "synthwave-84",
385
+ "tokyo-night",
386
+ "vesper",
387
+ "vitesse-black",
388
+ "vitesse-dark",
389
+ "vitesse-light",
390
+ ]
391
+
392
+
393
+ class ShikiBaseTransformers(Base):
394
+ """Base for creating transformers."""
395
+
396
+ library: str
397
+ fns: list[FunctionStringVar]
398
+ style: Optional[Style]
399
+
400
+
401
+ class ShikiJsTransformer(ShikiBaseTransformers):
402
+ """A Wrapped shikijs transformer."""
403
+
404
+ library: str = "@shikijs/transformers"
405
+ fns: list[FunctionStringVar] = [
406
+ FunctionStringVar.create(fn) for fn in SHIKIJS_TRANSFORMER_FNS
407
+ ]
408
+ style: Optional[Style] = Style(
409
+ {
410
+ "code": {"line-height": "1.7", "font-size": "0.875em", "display": "grid"},
411
+ # Diffs
412
+ ".diff": {
413
+ "margin": "0 -24px",
414
+ "padding": "0 24px",
415
+ "width": "calc(100% + 48px)",
416
+ "display": "inline-block",
417
+ },
418
+ ".diff.add": {
419
+ "background-color": "rgba(16, 185, 129, .14)",
420
+ "position": "relative",
421
+ },
422
+ ".diff.remove": {
423
+ "background-color": "rgba(244, 63, 94, .14)",
424
+ "opacity": "0.7",
425
+ "position": "relative",
426
+ },
427
+ ".diff.remove:after": {
428
+ "position": "absolute",
429
+ "left": "10px",
430
+ "content": "'-'",
431
+ "color": "#b34e52",
432
+ },
433
+ ".diff.add:after": {
434
+ "position": "absolute",
435
+ "left": "10px",
436
+ "content": "'+'",
437
+ "color": "#18794e",
438
+ },
439
+ # Highlight
440
+ ".highlighted": {
441
+ "background-color": "rgba(142, 150, 170, .14)",
442
+ "margin": "0 -24px",
443
+ "padding": "0 24px",
444
+ "width": "calc(100% + 48px)",
445
+ "display": "inline-block",
446
+ },
447
+ ".highlighted.error": {
448
+ "background-color": "rgba(244, 63, 94, .14)",
449
+ },
450
+ ".highlighted.warning": {
451
+ "background-color": "rgba(234, 179, 8, .14)",
452
+ },
453
+ # Highlighted Word
454
+ ".highlighted-word": {
455
+ "background-color": color("gray", 2),
456
+ "border": f"1px solid {color('gray', 5)}",
457
+ "padding": "1px 3px",
458
+ "margin": "-1px -3px",
459
+ "border-radius": "4px",
460
+ },
461
+ # Focused Lines
462
+ ".has-focused .line:not(.focused)": {
463
+ "opacity": "0.7",
464
+ "filter": "blur(0.095rem)",
465
+ "transition": "filter .35s, opacity .35s",
466
+ },
467
+ ".has-focused:hover .line:not(.focused)": {
468
+ "opacity": "1",
469
+ "filter": "none",
470
+ },
471
+ # White Space
472
+ # ".tab, .space": {
473
+ # "position": "relative",
474
+ # },
475
+ # ".tab::before": {
476
+ # "content": "'⇥'",
477
+ # "position": "absolute",
478
+ # "opacity": "0.3",
479
+ # },
480
+ # ".space::before": {
481
+ # "content": "'·'",
482
+ # "position": "absolute",
483
+ # "opacity": "0.3",
484
+ # },
485
+ }
486
+ )
487
+
488
+ def __init__(self, **kwargs):
489
+ """Initialize the transformer.
490
+
491
+ Args:
492
+ kwargs: Kwargs to initialize the props.
493
+
494
+ """
495
+ fns = kwargs.pop("fns", None)
496
+ style = kwargs.pop("style", None)
497
+ if fns:
498
+ kwargs["fns"] = [
499
+ (
500
+ FunctionStringVar.create(x)
501
+ if not isinstance(x, FunctionStringVar)
502
+ else x
503
+ )
504
+ for x in fns
505
+ ]
506
+
507
+ if style:
508
+ kwargs["style"] = Style(style)
509
+ super().__init__(**kwargs)
510
+
511
+
512
+ class ShikiCodeBlock(Component):
513
+ """A Code block."""
514
+
515
+ library = "/components/shiki/code"
516
+
517
+ tag = "Code"
518
+
519
+ alias = "ShikiCode"
520
+
521
+ lib_dependencies: list[str] = ["shiki"]
522
+
523
+ # The language to use.
524
+ language: Var[LiteralCodeLanguage] = Var.create("python")
525
+
526
+ # The theme to use ("light" or "dark").
527
+ theme: Var[LiteralCodeTheme] = Var.create("one-light")
528
+
529
+ # The set of themes to use for different modes.
530
+ themes: Var[Union[list[dict[str, Any]], dict[str, str]]]
531
+
532
+ # The code to display.
533
+ code: Var[str]
534
+
535
+ # The transformers to use for the syntax highlighter.
536
+ transformers: Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]] = Var.create(
537
+ []
538
+ )
539
+
540
+ @classmethod
541
+ def create(
542
+ cls,
543
+ *children,
544
+ **props,
545
+ ) -> Component:
546
+ """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
547
+
548
+ Args:
549
+ *children: The children of the component.
550
+ **props: The props to pass to the component.
551
+
552
+ Returns:
553
+ The code block component.
554
+ """
555
+ # Separate props for the code block and the wrapper
556
+ code_block_props = {}
557
+ code_wrapper_props = {}
558
+
559
+ class_props = cls.get_props()
560
+
561
+ # Distribute props between the code block and wrapper
562
+ for key, value in props.items():
563
+ (code_block_props if key in class_props else code_wrapper_props)[key] = (
564
+ value
565
+ )
566
+
567
+ code_block_props["code"] = children[0]
568
+ code_block = super().create(**code_block_props)
569
+
570
+ transformer_styles = {}
571
+ # Collect styles from transformers and wrapper
572
+ for transformer in code_block.transformers._var_value: # type: ignore
573
+ if isinstance(transformer, ShikiBaseTransformers) and transformer.style:
574
+ transformer_styles.update(transformer.style)
575
+ transformer_styles.update(code_wrapper_props.pop("style", {}))
576
+
577
+ return Box.create(
578
+ code_block,
579
+ *children[1:],
580
+ style=Style({**transformer_styles, **BOX_PARENT_STYLING}),
581
+ **code_wrapper_props,
582
+ )
583
+
584
+ def add_imports(self) -> dict[str, list[str]]:
585
+ """Add the necessary imports.
586
+ We add all referenced transformer functions as imports from their corresponding
587
+ libraries.
588
+
589
+ Returns:
590
+ Imports for the component.
591
+ """
592
+ imports = defaultdict(list)
593
+ for transformer in self.transformers._var_value:
594
+ if isinstance(transformer, ShikiBaseTransformers):
595
+ imports[transformer.library].extend(
596
+ [ImportVar(tag=str(fn)) for fn in transformer.fns]
597
+ )
598
+ (
599
+ self.lib_dependencies.append(transformer.library)
600
+ if transformer.library not in self.lib_dependencies
601
+ else None
602
+ )
603
+ return imports
604
+
605
+ @classmethod
606
+ def create_transformer(cls, library: str, fns: list[str]) -> ShikiBaseTransformers:
607
+ """Create a transformer from a third party library.
608
+
609
+ Args:
610
+ library: The name of the library.
611
+ fns: The str names of the functions/callables to invoke from the library.
612
+
613
+ Returns:
614
+ A transformer for the specified library.
615
+
616
+ Raises:
617
+ ValueError: If a supplied function name is not valid str.
618
+ """
619
+ if any(not isinstance(fn_name, str) for fn_name in fns):
620
+ raise ValueError(
621
+ f"the function names should be str names of functions in the specified transformer: {library!r}"
622
+ )
623
+ return ShikiBaseTransformers( # type: ignore
624
+ library=library, fns=[FunctionStringVar.create(fn) for fn in fns]
625
+ )
626
+
627
+ def _render(self, props: dict[str, Any] | None = None):
628
+ """Renders the component with the given properties, processing transformers if present.
629
+
630
+ Args:
631
+ props: Optional properties to pass to the render function.
632
+
633
+ Returns:
634
+ Rendered component output.
635
+ """
636
+ # Ensure props is initialized from class attributes if not provided
637
+ props = props or {
638
+ attr.rstrip("_"): getattr(self, attr) for attr in self.get_props()
639
+ }
640
+
641
+ # Extract transformers and apply transformations
642
+ transformers = props.get("transformers")
643
+ if transformers is not None:
644
+ transformed_values = self._process_transformers(transformers._var_value)
645
+ props["transformers"] = LiteralVar.create(transformed_values)
646
+
647
+ return super()._render(props)
648
+
649
+ def _process_transformers(self, transformer_list: list) -> list:
650
+ """Processes a list of transformers, applying transformations where necessary.
651
+
652
+ Args:
653
+ transformer_list: List of transformer objects or values.
654
+
655
+ Returns:
656
+ list: A list of transformed values.
657
+ """
658
+ processed = []
659
+
660
+ for transformer in transformer_list:
661
+ if isinstance(transformer, ShikiBaseTransformers):
662
+ processed.extend(fn.call() for fn in transformer.fns)
663
+ else:
664
+ processed.append(transformer)
665
+
666
+ return processed
667
+
668
+
669
+ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
670
+ """High level component for the shiki syntax highlighter."""
671
+
672
+ # If this is enabled, the default transformers(shikijs transformer) will be used.
673
+ use_transformers: Var[bool]
674
+
675
+ # If this is enabled line numbers will be shown next to the code block.
676
+ show_line_numbers: Var[bool]
677
+
678
+ # Whether a copy button should appear.
679
+ can_copy: Var[bool] = Var.create(False)
680
+
681
+ # copy_button: A custom copy button to override the default one.
682
+ copy_button: Var[Optional[Union[Component, bool]]] = Var.create(None)
683
+
684
+ @classmethod
685
+ def create(
686
+ cls,
687
+ *children,
688
+ **props,
689
+ ) -> Component:
690
+ """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/).
691
+
692
+ Args:
693
+ *children: The children of the component.
694
+ **props: The props to pass to the component.
695
+
696
+ Returns:
697
+ The code block component.
698
+ """
699
+ use_transformers = props.pop("use_transformers", False)
700
+ show_line_numbers = props.pop("show_line_numbers", False)
701
+ language = props.pop("language", None)
702
+ can_copy = props.pop("can_copy", False)
703
+ copy_button = props.pop("copy_button", None)
704
+
705
+ if use_transformers:
706
+ props["transformers"] = [ShikiJsTransformer()]
707
+
708
+ if language is not None:
709
+ props["language"] = cls._map_languages(language)
710
+
711
+ # line numbers are generated via css
712
+ if show_line_numbers:
713
+ props["style"] = {**LINE_NUMBER_STYLING, **props.get("style", {})}
714
+
715
+ theme = props.pop("theme", None)
716
+ props["theme"] = props["theme"] = (
717
+ cls._map_themes(theme)
718
+ if theme is not None
719
+ else color_mode_cond( # Default color scheme responds to global color mode.
720
+ light="one-light",
721
+ dark="one-dark-pro",
722
+ )
723
+ )
724
+
725
+ if can_copy:
726
+ code = children[0]
727
+ copy_button = ( # type: ignore
728
+ copy_button
729
+ if copy_button is not None
730
+ else Button.create(
731
+ Icon.create(tag="copy", size=16, color=color("gray", 11)),
732
+ on_click=[
733
+ set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore
734
+ copy_script(),
735
+ ],
736
+ style=Style(
737
+ {
738
+ "position": "absolute",
739
+ "top": "4px",
740
+ "right": "4px",
741
+ "background": color("gray", 3),
742
+ "border": "1px solid",
743
+ "border-color": color("gray", 5),
744
+ "border-radius": "6px",
745
+ "padding": "5px",
746
+ "opacity": "1",
747
+ "cursor": "pointer",
748
+ "_hover": {
749
+ "background": color("gray", 4),
750
+ },
751
+ "transition": "background 0.250s ease-out",
752
+ "&>svg": {
753
+ "transition": "transform 0.250s ease-out, opacity 0.250s ease-out",
754
+ },
755
+ "_active": {
756
+ "background": color("gray", 5),
757
+ },
758
+ }
759
+ ),
760
+ )
761
+ )
762
+
763
+ if copy_button:
764
+ return ShikiCodeBlock.create(
765
+ children[0], copy_button, position="relative", **props
766
+ )
767
+ else:
768
+ return ShikiCodeBlock.create(children[0], **props)
769
+
770
+ @staticmethod
771
+ def _map_themes(theme: str) -> str:
772
+ if isinstance(theme, str) and theme in THEME_MAPPING:
773
+ return THEME_MAPPING[theme]
774
+ return theme
775
+
776
+ @staticmethod
777
+ def _map_languages(language: str) -> str:
778
+ if isinstance(language, str) and language in LANGUAGE_MAPPING:
779
+ return LANGUAGE_MAPPING[language]
780
+ return language
781
+
782
+ @staticmethod
783
+ def _strip_transformer_triggers(code: str | StringVar) -> StringVar | str:
784
+ if not isinstance(code, (StringVar, str)):
785
+ raise VarTypeError(
786
+ f"code should be string literal or a StringVar type. Got {type(code)} instead."
787
+ )
788
+ regex_pattern = r"[\/#]+ *\[!code.*?\]"
789
+
790
+ if isinstance(code, Var):
791
+ return string_replace_operation(
792
+ code, StringVar(_js_expr=f"/{regex_pattern}/g", _var_type=str), ""
793
+ )
794
+ if isinstance(code, str):
795
+ return re.sub(regex_pattern, "", code)
796
+
797
+
798
+ class TransformerNamespace(ComponentNamespace):
799
+ """Namespace for the Transformers."""
800
+
801
+ shikijs = ShikiJsTransformer
802
+
803
+
804
+ class CodeblockNamespace(ComponentNamespace):
805
+ """Namespace for the CodeBlock component."""
806
+
807
+ root = staticmethod(ShikiCodeBlock.create)
808
+ create_transformer = staticmethod(ShikiCodeBlock.create_transformer)
809
+ transformers = TransformerNamespace()
810
+ __call__ = staticmethod(ShikiHighLevelCodeBlock.create)
811
+
812
+
813
+ code_block = CodeblockNamespace()