instaui 0.1.3__py3-none-any.whl → 0.1.5__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.
Files changed (93) hide show
  1. instaui/components/content.py +4 -4
  2. instaui/components/echarts/echarts.js +128 -0
  3. instaui/components/echarts/echarts.py +194 -0
  4. instaui/components/echarts/static/echarts.esm.min.js +45 -0
  5. instaui/components/element.py +103 -13
  6. instaui/components/html/__init__.py +31 -18
  7. instaui/components/html/_preset.py +4 -0
  8. instaui/components/html/heading.py +51 -0
  9. instaui/components/html/range.py +3 -0
  10. instaui/components/html/select.py +16 -35
  11. instaui/components/html/table.py +36 -0
  12. instaui/components/html/textarea.py +28 -0
  13. instaui/components/markdown/markdown.js +33 -0
  14. instaui/components/markdown/markdown.py +41 -0
  15. instaui/components/markdown/static/github-markdown.css +12 -0
  16. instaui/components/markdown/static/marked.esm.js +2579 -0
  17. instaui/components/shiki_code/shiki_code.js +126 -0
  18. instaui/components/shiki_code/shiki_code.py +99 -0
  19. instaui/components/shiki_code/static/langs/css.mjs +5 -0
  20. instaui/components/shiki_code/static/langs/markdown.mjs +5 -0
  21. instaui/components/shiki_code/static/langs/python.mjs +5 -0
  22. instaui/components/shiki_code/static/langs/shell.mjs +2 -0
  23. instaui/components/shiki_code/static/langs/shellscript.mjs +5 -0
  24. instaui/components/shiki_code/static/shiki-core.js +5784 -0
  25. instaui/components/shiki_code/static/shiki-style.css +175 -0
  26. instaui/components/shiki_code/static/shiki-transformers.js +461 -0
  27. instaui/components/shiki_code/static/themes/vitesse-dark.mjs +2 -0
  28. instaui/components/shiki_code/static/themes/vitesse-light.mjs +2 -0
  29. instaui/components/value_element.py +7 -3
  30. instaui/components/vfor.py +1 -1
  31. instaui/consts.py +2 -1
  32. instaui/daisyui/__init__.py +26 -0
  33. instaui/daisyui/_index.py +20 -0
  34. instaui/daisyui/button.py +38 -0
  35. instaui/daisyui/checkbox.py +17 -0
  36. instaui/daisyui/static/daisyui.css +1 -0
  37. instaui/daisyui/static/themes.css +1 -0
  38. instaui/daisyui/table.py +35 -0
  39. instaui/dependencies/component_dependency.py +11 -5
  40. instaui/event/js_event.py +1 -0
  41. instaui/event/web_event.py +6 -7
  42. instaui/fastapi_server/dependency_router.py +4 -3
  43. instaui/fastapi_server/resource.py +12 -16
  44. instaui/fastapi_server/server.py +34 -24
  45. instaui/handlers/event_handler.py +3 -1
  46. instaui/handlers/watch_handler.py +4 -0
  47. instaui/html_tools.py +44 -2
  48. instaui/inject.py +3 -3
  49. instaui/runtime/_app.py +43 -4
  50. instaui/runtime/_link_manager.py +89 -0
  51. instaui/runtime/resource.py +21 -8
  52. instaui/shadcn_classless/_index.py +42 -0
  53. instaui/shadcn_classless/static/shadcn-classless.css +403 -0
  54. instaui/spa_router/_functions.py +1 -1
  55. instaui/spa_router/_route_model.py +1 -1
  56. instaui/static/insta-ui.css +1 -1
  57. instaui/static/insta-ui.esm-browser.prod.js +1308 -1252
  58. instaui/static/insta-ui.js.map +1 -1
  59. instaui/static/instaui-tools-browser.js +511 -0
  60. instaui/static/templates/webview.html +78 -0
  61. instaui/systems/module_system.py +30 -0
  62. instaui/tailwind/__init__.py +6 -0
  63. instaui/tailwind/_index.py +24 -0
  64. instaui/{static/tailwindcss.min.js → tailwind/static/tailwindcss-v3.min.js} +62 -62
  65. instaui/tailwind/static/tailwindcss-v4.min.js +8 -0
  66. instaui/template/_utils.py +23 -0
  67. instaui/template/webview_template.py +50 -0
  68. instaui/template/zero_template.py +18 -17
  69. instaui/ui/__build_init.py +73 -0
  70. instaui/ui/__init__.py +74 -58
  71. instaui/ui/__init__.pyi +135 -0
  72. instaui/ui/events.py +1 -1
  73. instaui/ui_functions/server.py +3 -1
  74. instaui/vars/event_context.py +4 -0
  75. instaui/vars/web_computed.py +30 -30
  76. instaui/watch/web_watch.py +5 -6
  77. instaui/webview/__init__.py +1 -0
  78. instaui/webview/_utils.py +8 -0
  79. instaui/webview/api.py +72 -0
  80. instaui/webview/func.py +114 -0
  81. instaui/webview/index.py +162 -0
  82. instaui/webview/resource.py +172 -0
  83. instaui/zero/func.py +31 -23
  84. instaui/zero/scope.py +110 -4
  85. {instaui-0.1.3.dist-info → instaui-0.1.5.dist-info}/METADATA +4 -1
  86. {instaui-0.1.3.dist-info → instaui-0.1.5.dist-info}/RECORD +88 -44
  87. instaui/handlers/computed_handler.py +0 -42
  88. instaui/handlers/config_handler.py +0 -13
  89. instaui/static/insta-ui.iife.js +0 -29
  90. instaui/static/insta-ui.iife.js.map +0 -1
  91. instaui/zero/test.html +0 -44
  92. {instaui-0.1.3.dist-info → instaui-0.1.5.dist-info}/LICENSE +0 -0
  93. {instaui-0.1.3.dist-info → instaui-0.1.5.dist-info}/WHEEL +0 -0
@@ -7,10 +7,13 @@ from pathlib import Path
7
7
  import re
8
8
  from typing import (
9
9
  Any,
10
+ Callable,
10
11
  Dict,
12
+ Iterable,
11
13
  List,
12
14
  ClassVar,
13
15
  Optional,
16
+ Set,
14
17
  Tuple,
15
18
  Union,
16
19
  cast,
@@ -35,6 +38,7 @@ from instaui.vars.mixin_types.element_binding import ElementBindingMixin
35
38
 
36
39
  if TYPE_CHECKING:
37
40
  from instaui.event.event_mixin import EventMixin
41
+ from instaui.vars.types import TMaybeRef
38
42
 
39
43
 
40
44
  # Refer to the NiceGUI project.
@@ -103,7 +107,7 @@ class Element(Component):
103
107
  cls,
104
108
  *,
105
109
  esm: Union[str, Path, None] = None,
106
- externals: Optional[List[Union[str, Path]]] = None,
110
+ externals: Optional[Dict[str, Path]] = None,
107
111
  css: Union[List[Union[str, Path]], None] = None,
108
112
  ) -> None:
109
113
  super().__init_subclass__()
@@ -112,18 +116,21 @@ class Element(Component):
112
116
  esm = _make_dependency_path(esm, cls)
113
117
 
114
118
  if externals:
115
- externals = [_make_dependency_path(e, cls) for e in externals]
119
+ externals = {
120
+ key: _make_dependency_path(value, cls)
121
+ for key, value in externals.items()
122
+ }
116
123
 
117
124
  if css:
118
- css = [_make_dependency_path(c, cls) for c in css]
125
+ css = set(_make_dependency_path(c, cls) for c in css) # type: ignore
119
126
 
120
127
  tag_name = f"instaui-{esm.stem}"
121
128
 
122
129
  cls.dependency = ComponentDependencyInfo(
123
130
  tag_name=tag_name,
124
131
  esm=esm,
125
- externals=cast(List[Path], externals or []),
126
- css=cast(List[Path], css or []),
132
+ externals=cast(Dict[str, Path], externals or {}),
133
+ css=cast(Set[Path], css or set()),
127
134
  )
128
135
 
129
136
  cls._default_props = copy(cls._default_props)
@@ -225,32 +232,53 @@ class Element(Component):
225
232
  @overload
226
233
  def classes(self, add: str) -> Self: ...
227
234
  @overload
228
- def classes(self, add: Dict[str, ElementBindingMixin[bool]]) -> Self: ...
235
+ def classes(self, add: Dict[str, TMaybeRef[bool]]) -> Self: ...
229
236
 
230
237
  @overload
231
- def classes(self, add: ElementBindingMixin[str]) -> Self: ...
238
+ def classes(self, add: TMaybeRef[str]) -> Self: ...
232
239
 
233
240
  def classes(
234
241
  self,
235
242
  add: Union[
236
243
  str,
237
- Dict[str, ElementBindingMixin[bool]],
238
- ElementBindingMixin[str],
244
+ Dict[str, TMaybeRef[bool]],
245
+ TMaybeRef[str],
239
246
  VForItem,
240
247
  ],
241
248
  ) -> Self:
249
+ """Add classes to the component.
250
+
251
+ Args:
252
+ add (Union[ str, Dict[str, TMaybeRef[bool]], TMaybeRef[str], VForItem, ]): classes to add.
253
+
254
+
255
+ Examples:
256
+ .. code-block:: python
257
+
258
+ elemelt = html.span('test')
259
+ elemelt.classes('class1 class2')
260
+
261
+ # dynamically classes
262
+ class_name = ui.state('x')
263
+ elemelt.classes(class_name)
264
+
265
+ # apply name if True
266
+ apply = ui.state(True)
267
+ elemelt.classes({'x': apply})
268
+ """
269
+
242
270
  if isinstance(add, str):
243
271
  self._str_classes = self._update_classes(self._str_classes, add)
244
272
 
245
273
  if isinstance(add, dict):
246
- self._dict_classes.update(**add)
274
+ self._dict_classes.update(**add) # type: ignore
247
275
 
248
276
  if isinstance(add, ElementBindingMixin):
249
277
  self._bind_str_classes.append(add) # type: ignore
250
278
 
251
279
  return self
252
280
 
253
- def style(self, add: Union[str, Dict[str, Any], ElementBindingMixin[str]]) -> Self:
281
+ def style(self, add: Union[str, Dict[str, Any], TMaybeRef[str]]) -> Self:
254
282
  if isinstance(add, dict):
255
283
  add = {key: value for key, value in add.items()}
256
284
 
@@ -262,7 +290,7 @@ class Element(Component):
262
290
  self._style.update(new_style)
263
291
  return self
264
292
 
265
- def props(self, add: Union[str, Dict[str, Any], ElementBindingMixin]) -> Self:
293
+ def props(self, add: Union[str, Dict[str, Any], TMaybeRef]) -> Self:
266
294
  if isinstance(add, ElementBindingMixin):
267
295
  self._proxy_props.append(add)
268
296
  return self
@@ -326,6 +354,64 @@ class Element(Component):
326
354
  self._element_ref = ref
327
355
  return self
328
356
 
357
+ def update_dependencies(
358
+ self,
359
+ *,
360
+ css: Optional[Iterable[Path]] = None,
361
+ externals: Optional[Dict[str, Path]] = None,
362
+ replace: bool = False,
363
+ ):
364
+ if not self.dependency:
365
+ return
366
+
367
+ app = get_app_slot()
368
+ dep = self.dependency.copy()
369
+ if replace:
370
+ dep.css.clear()
371
+ dep.externals.clear()
372
+
373
+ if css:
374
+ dep.css.update(css)
375
+
376
+ if externals:
377
+ dep.externals.update(externals)
378
+
379
+ app.add_temp_component_dependency(dep)
380
+
381
+ def use(self, *use_fns: Callable[[Self], None]) -> Self:
382
+ """Use functions to the component object.
383
+
384
+ Args:
385
+ use_fns (Callable[[Self], None]): The list of use functions.
386
+
387
+ Examples:
388
+ .. code-block:: python
389
+ def use_red_color(element: html.paragraph):
390
+ element.style('color: red')
391
+
392
+ html.paragraph('Hello').use(use_red_color)
393
+ """
394
+
395
+ for fn in use_fns:
396
+ fn(self)
397
+ return self
398
+
399
+ @classmethod
400
+ def use_init(cls, init_fn: Callable[[type[Self]], Self]) -> Self:
401
+ """Use this method to initialize the component.
402
+
403
+ Args:
404
+ init_fn (Callable[[type[Self]], Self]): The initialization function.
405
+
406
+ Examples:
407
+ .. code-block:: python
408
+ def fack_init(cls: type[html.table]) -> html.table:
409
+ return cls(columns=['name', 'age'],rows = [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}])
410
+
411
+ ui.table.use_init(fack_init)
412
+ """
413
+ return init_fn(cls)
414
+
329
415
  def _to_json_dict(self):
330
416
  data = super()._to_json_dict()
331
417
 
@@ -376,7 +462,11 @@ class Element(Component):
376
462
  data["dir"] = list(self._directives.keys())
377
463
 
378
464
  if self.dependency:
379
- get_app_slot().use_component_dependency(self.dependency)
465
+ app_slot = get_app_slot()
466
+ tag_name = self.dependency.tag_name
467
+ app_slot.use_component_dependency(
468
+ app_slot.get_temp_component_dependency(tag_name, self.dependency)
469
+ )
380
470
 
381
471
  if self._element_ref:
382
472
  scope = get_current_scope()
@@ -1,21 +1,3 @@
1
- from .span import Span as span
2
- from .label import Label as label
3
- from .paragraph import Paragraph as paragraph
4
- from .input import Input as input
5
- from .number import Number as number
6
- from .button import Button as button
7
- from .checkbox import Checkbox as checkbox
8
- from .form import Form as form
9
- from .select import Select as select
10
- from .ul import Ul as ul
11
- from .li import Li as li
12
- from .div import Div as div
13
- from .range import Range as range
14
- from .date import Date as date
15
- from .link import Link as link
16
-
17
- option = select.Option
18
-
19
1
  __all__ = [
20
2
  "span",
21
3
  "label",
@@ -33,4 +15,35 @@ __all__ = [
33
15
  "range",
34
16
  "date",
35
17
  "link",
18
+ "textarea",
19
+ "table",
20
+ "h1",
21
+ "h2",
22
+ "h3",
23
+ "h4",
24
+ "h5",
25
+ "h6",
36
26
  ]
27
+
28
+ from .span import Span as span
29
+ from .label import Label as label
30
+ from .paragraph import Paragraph as paragraph
31
+ from .input import Input as input
32
+ from .number import Number as number
33
+ from .button import Button as button
34
+ from .checkbox import Checkbox as checkbox
35
+ from .form import Form as form
36
+ from .select import Select as select
37
+ from .ul import Ul as ul
38
+ from .li import Li as li
39
+ from .div import Div as div
40
+ from .range import Range as range
41
+ from .date import Date as date
42
+ from .link import Link as link
43
+ from .textarea import Textarea as textarea
44
+ from .table import Table as table
45
+ from .heading import H1 as h1, H2 as h2, H3 as h3, H4 as h4, H5 as h5, H6 as h6
46
+
47
+ option = select.Option
48
+
49
+ from . import _preset # noqa: E402, F401
@@ -0,0 +1,4 @@
1
+ from instaui.shadcn_classless._index import use_shadcn_classless
2
+
3
+
4
+ use_shadcn_classless()
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Any, Literal, Union
3
+ from instaui.components.element import Element
4
+
5
+ if TYPE_CHECKING:
6
+ import instaui.vars as ui_vars
7
+
8
+
9
+ class Heading(Element):
10
+ def __init__(
11
+ self,
12
+ text: Union[str, ui_vars.TMaybeRef[Any]],
13
+ *,
14
+ level: Literal[1, 2, 3, 4, 5, 6] = 1,
15
+ ):
16
+ super().__init__(f"h{level}")
17
+ self.props(
18
+ {
19
+ "innerText": text,
20
+ }
21
+ )
22
+
23
+
24
+ class H1(Heading):
25
+ def __init__(self, text: Union[str, ui_vars.TMaybeRef[Any]]):
26
+ super().__init__(text, level=1)
27
+
28
+
29
+ class H2(Heading):
30
+ def __init__(self, text: Union[str, ui_vars.TMaybeRef[Any]]):
31
+ super().__init__(text, level=2)
32
+
33
+
34
+ class H3(Heading):
35
+ def __init__(self, text: Union[str, ui_vars.TMaybeRef[Any]]):
36
+ super().__init__(text, level=3)
37
+
38
+
39
+ class H4(Heading):
40
+ def __init__(self, text: Union[str, ui_vars.TMaybeRef[Any]]):
41
+ super().__init__(text, level=4)
42
+
43
+
44
+ class H5(Heading):
45
+ def __init__(self, text: Union[str, ui_vars.TMaybeRef[Any]]):
46
+ super().__init__(text, level=5)
47
+
48
+
49
+ class H6(Heading):
50
+ def __init__(self, text: Union[str, ui_vars.TMaybeRef[Any]]):
51
+ super().__init__(text, level=6)
@@ -17,6 +17,7 @@ class Range(InputEventMixin, ValueElement[_T_value]):
17
17
  *,
18
18
  min: Union[_T_value, TMaybeRef[_T_value], None] = None,
19
19
  max: Union[_T_value, TMaybeRef[_T_value], None] = None,
20
+ step: Union[_T_value, TMaybeRef[_T_value], None] = None,
20
21
  ):
21
22
  super().__init__("input", value, is_html_component=True)
22
23
  self.props({"type": "range"})
@@ -25,6 +26,8 @@ class Range(InputEventMixin, ValueElement[_T_value]):
25
26
  self.props({"min": min})
26
27
  if max is not None:
27
28
  self.props({"max": max})
29
+ if step is not None:
30
+ self.props({"step": step})
28
31
 
29
32
  def vmodel(
30
33
  self,
@@ -1,72 +1,53 @@
1
1
  from __future__ import annotations
2
2
  from typing import (
3
3
  TYPE_CHECKING,
4
- Any,
5
4
  Callable,
6
5
  Dict,
7
6
  List,
8
- Literal,
9
7
  Optional,
10
8
  Union,
11
9
  overload,
12
10
  )
13
11
  from typing_extensions import Self
14
-
15
- from instaui.vars import Ref
16
12
  from instaui.components.value_element import ValueElement
17
13
  from instaui.components.element import Element
14
+ from instaui.event.event_mixin import EventMixin
18
15
  from instaui.vars.types import TMaybeRef
19
16
  from instaui.components.vfor import VFor
20
17
 
21
- if TYPE_CHECKING:
22
- import instaui.vars as ui_vars
23
-
24
-
25
18
  _T_Select_Value = Union[List[str], str]
26
19
 
27
20
 
28
21
  class Select(ValueElement[Union[List[str], str]]):
29
22
  def __init__(
30
23
  self,
31
- value: Union[_T_Select_Value, ui_vars.TMaybeRef[_T_Select_Value], None] = None,
24
+ value: Union[_T_Select_Value, TMaybeRef[_T_Select_Value], None] = None,
25
+ *,
26
+ model_value: Union[str, TMaybeRef[str], None] = None,
32
27
  ):
33
28
  super().__init__("select", value, is_html_component=True)
34
29
 
35
- def vmodel(
36
- self,
37
- value: Ref[Any],
38
- *modifiers: Literal["lazy"],
39
- ):
40
- return super().vmodel(value, *modifiers) # type: ignore
41
-
42
- @overload
43
- def on_change(self, handler: Callable, *, key: Optional[str] = None) -> Self: ...
44
-
45
- @overload
46
- def on_change(
47
- self,
48
- handler: str,
49
- *,
50
- bindings: Optional[Dict] = None,
51
- key: Optional[str] = None,
52
- ) -> Self: ...
30
+ if model_value is not None:
31
+ self.props({"value": model_value})
53
32
 
54
33
  def on_change(
55
34
  self,
56
- handler: Union[Callable, str],
35
+ handler: EventMixin,
57
36
  *,
58
- bindings: Optional[Dict] = None,
59
- key: Optional[str] = None,
37
+ extends: Optional[List] = None,
60
38
  ):
61
- self.on("change", handler, bindings=bindings, key=key) # type: ignore
39
+ self.on("change", handler)
62
40
  return self
63
41
 
64
42
  @classmethod
65
43
  def from_list(
66
44
  cls,
67
45
  options: TMaybeRef[List],
46
+ value: Union[_T_Select_Value, TMaybeRef[_T_Select_Value], None] = None,
47
+ *,
48
+ model_value: Union[str, TMaybeRef[str], None] = None,
68
49
  ) -> Select:
69
- with cls() as select:
50
+ with cls(value, model_value=model_value) as select:
70
51
  with VFor(options) as item:
71
52
  Select.Option(item) # type: ignore
72
53
 
@@ -75,9 +56,9 @@ class Select(ValueElement[Union[List[str], str]]):
75
56
  class Option(Element):
76
57
  def __init__(
77
58
  self,
78
- text: Optional[ui_vars.TMaybeRef[str]] = None,
79
- value: Optional[ui_vars.TMaybeRef[str]] = None,
80
- disabled: Optional[ui_vars.TMaybeRef[bool]] = None,
59
+ text: Optional[TMaybeRef[str]] = None,
60
+ value: Optional[TMaybeRef[str]] = None,
61
+ disabled: Optional[TMaybeRef[bool]] = None,
81
62
  ):
82
63
  props = {
83
64
  key: value
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Any, List, Optional, Union
3
+ from instaui.components.element import Element
4
+ from instaui.components.content import Content
5
+ from instaui.components.vfor import VFor
6
+
7
+ if TYPE_CHECKING:
8
+ import instaui.vars as ui_vars
9
+
10
+
11
+ class Table(Element):
12
+ def __init__(
13
+ self,
14
+ columns: Union[List[str], ui_vars.TMaybeRef[List[str]], None] = None,
15
+ rows: Union[List[List[Any]], ui_vars.TMaybeRef[List[List[Any]]], None] = None,
16
+ ):
17
+ """Create a table element.
18
+
19
+ Args:
20
+ columns (Union[List[str], ui_vars.TMaybeRef[List[str]], None], optional): A list of column headers or a reactive reference to such a list. Defaults to None.
21
+ rows (Union[List[List[Any]], ui_vars.TMaybeRef[List[List[Any]]], None], optional): A list of row data, where each row is a list of cell values, or a reactive reference to such a list. Defaults to None.
22
+ """
23
+ super().__init__("table")
24
+
25
+ with self:
26
+ with Element("thead"), Element("tr"):
27
+ with VFor(columns) as col: # type: ignore
28
+ with Element("th"):
29
+ Content(col)
30
+
31
+ with Element("tbody"):
32
+ with VFor(rows) as row: # type: ignore
33
+ with Element("tr"):
34
+ with VFor(row) as cell: # type: ignore
35
+ with Element("td"):
36
+ Content(cell)
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Optional, Union
3
+ from instaui.components.element import Element
4
+ from instaui.components.value_element import ValueElement
5
+
6
+ from ._mixins import InputEventMixin
7
+
8
+ if TYPE_CHECKING:
9
+ import instaui.vars as ui_vars
10
+
11
+
12
+ class Textarea(InputEventMixin, ValueElement[str]):
13
+ def __init__(
14
+ self,
15
+ value: Union[str, ui_vars.TMaybeRef[str], None] = None,
16
+ *,
17
+ model_value: Union[str, ui_vars.TMaybeRef[str], None] = None,
18
+ disabled: Optional[ui_vars.TMaybeRef[bool]] = None,
19
+ ):
20
+ super().__init__("textarea", value, is_html_component=True)
21
+
22
+ if disabled is not None:
23
+ self.props({"disabled": disabled})
24
+ if model_value is not None:
25
+ self.props({"value": model_value})
26
+
27
+ def _input_event_mixin_element(self) -> Element:
28
+ return self
@@ -0,0 +1,33 @@
1
+ import { h, computed } from "vue";
2
+ import { marked } from "marked";
3
+
4
+ export default {
5
+ props: ['content'],
6
+ setup(props) {
7
+
8
+ const md = computed(() => marked.parse(
9
+ cleanMultilineString(props.content)
10
+ ))
11
+
12
+ return () => h("div", { class: 'markdown-body', innerHTML: md.value });
13
+ }
14
+
15
+ }
16
+
17
+ function cleanMultilineString(text) {
18
+ const lines = text.split(/\r?\n/);
19
+
20
+ while (lines.length && lines[0].trim() === '') {
21
+ lines.shift();
22
+ }
23
+
24
+ while (lines.length && lines[lines.length - 1].trim() === '') {
25
+ lines.pop();
26
+ }
27
+
28
+ if (lines.length > 0) {
29
+ lines[0] = lines[0].replace(/^[\t ]+/, '');
30
+ }
31
+
32
+ return lines.join('\n');
33
+ }
@@ -0,0 +1,41 @@
1
+ from pathlib import Path
2
+ from instaui import ui
3
+
4
+ _STATIC_DIR = Path(__file__).parent / "static"
5
+ _CORE_JS_FILE = _STATIC_DIR / "marked.esm.js"
6
+ _GITHUB_MARKDOWN_CSS_FILE = _STATIC_DIR / "github-markdown.css"
7
+
8
+ _IMPORT_MAPS = {
9
+ "marked": _CORE_JS_FILE,
10
+ }
11
+
12
+
13
+ class Markdown(
14
+ ui.element,
15
+ esm="./markdown.js",
16
+ externals=_IMPORT_MAPS,
17
+ css=[_GITHUB_MARKDOWN_CSS_FILE],
18
+ ):
19
+ def __init__(self, content: ui.TMaybeRef[str]):
20
+ super().__init__()
21
+ self.props({"content": _clean_multiline_string(content)})
22
+
23
+
24
+ def _clean_multiline_string(text: ui.TMaybeRef[str]) -> ui.TMaybeRef[str]:
25
+ if not isinstance(text, str):
26
+ return text
27
+
28
+ if not text:
29
+ return ""
30
+
31
+ lines = text.splitlines()
32
+
33
+ while lines and lines[0].strip() == "":
34
+ lines.pop(0)
35
+ while lines and lines[-1].strip() == "":
36
+ lines.pop()
37
+
38
+ if lines:
39
+ lines[0] = lines[0].lstrip()
40
+
41
+ return "\n".join(lines)