HTeaLeaf 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,219 @@
1
+ import hashlib
2
+ import json
3
+ from typing import Any, List, Union
4
+
5
+ from ..Magic.jslib.JSCode import JSCode
6
+
7
+
8
+ class Component:
9
+ """
10
+ Represents an HTML component with attributes, children, and optional inline styles.
11
+ This class allows constructing HTML elements programmatically and managing CSS styles.
12
+ """
13
+
14
+ def __init__(self, name, *childs: Union[str, List[Any], "Component", "JSCode"]) -> None:
15
+ """
16
+ Initializes a new Component instance.
17
+
18
+ :param name: The tag name of the HTML element.
19
+ :param childs: Optional children elements, which can be strings, lists, or other Component instances.
20
+ """
21
+
22
+ self.styles: str | None = None
23
+ self.name = name
24
+ self.children: list[Component | str | list | JSCode] = list(childs)
25
+ self.attributes: dict[str, str | None] = dict()
26
+ self._id: str = self._generate_id()
27
+
28
+ def _generate_id(self) -> str:
29
+ """Genera un ID determinista basado en el contenido del componente."""
30
+ content = {
31
+ "name": self.name,
32
+ "children": [
33
+ child._id if isinstance(child, Component) else str(child)
34
+ for child in self.children
35
+ ]
36
+ }
37
+ raw = json.dumps(content, sort_keys=True)
38
+ hash_str = hashlib.md5(raw.encode()).hexdigest()[:12]
39
+ return f"tl-{hash_str}"
40
+
41
+ def id(self, id: str):
42
+ """
43
+ Sets the ID of the component and adds it as an attribute.
44
+
45
+ :param id: The ID to assign.
46
+ :return: The component instance (for method chaining).
47
+ """
48
+
49
+ self._id = id
50
+ return self.attr(id=id)
51
+
52
+ def classes(self, classes):
53
+ """
54
+ Adds a CSS class attribute to the component.
55
+
56
+ :param classes: CSS class names (space-separated).
57
+ :return: The component instance (for method chaining).
58
+ """
59
+
60
+ self.attributes["class"] = classes
61
+ return self
62
+
63
+ def style(self, path: str | None = None, **attr):
64
+ """
65
+ Adds inline styles to the component.
66
+
67
+ :param path: Optional path to an external CSS file.
68
+ :param attr: CSS properties to apply (e.g., color="red", margin="10px").
69
+ :return: The component instance (for method chaining).
70
+ """
71
+
72
+
73
+ self.styles = (self.styles or "") + f"#{self._id} {{\n"
74
+ self.styles += "\n".join(
75
+ f" {k.replace('_', '-')}: {v};" for k, v in attr.items()
76
+ )
77
+ self.styles += "\n}\n"
78
+
79
+ if path:
80
+ with open(path, "r") as f:
81
+ self.styles += f.read()
82
+ return self
83
+
84
+ def attr(self,*args, **attr):
85
+ """
86
+ Adds custom attributes to the component.
87
+
88
+ :param attr: Dictionary of attribute names and values.
89
+ :return: The component instance (for method chaining).
90
+ """
91
+
92
+ for arg in args:
93
+ self.attributes[arg] = None
94
+
95
+ for k in attr:
96
+ # if type(attr[k]) is str:
97
+ value = attr[k]
98
+ # if isinstance(value, JSCode):
99
+ # value = f"{{{{{str(value)}}}}}"
100
+ self.attributes[k] = value
101
+ # elif type(attr[k]) is FunctionType:
102
+ # py_f = inspect.getsource(attr[k])
103
+ # self.attributes[k] = f"""() => pyodide.runPython(`{py_f}`)"""
104
+
105
+ return self
106
+
107
+ def append(self, child: Union[str, "Component", list]):
108
+ """
109
+ Appends a child element to the component.
110
+
111
+ :param child: A Component, string, or list of elements.
112
+ :return: The component instance (for method chaining).
113
+ """
114
+
115
+ self.children.append(child)
116
+ return self
117
+
118
+ def prepend(self, child: Union[str, "Component", list]):
119
+ """
120
+ Prepends a child element to the component.
121
+
122
+ :param child: A Component, string, or list of elements.
123
+ :return: The component instance (for method chaining).
124
+ """
125
+
126
+ self.children.insert(0, child)
127
+ return self
128
+
129
+ def __build_attr__(self) -> str:
130
+ return " " + " ".join(
131
+ f"{k}='{v}'" if v is not None else f"{k}" for k, v in self.attributes.items()
132
+ )
133
+
134
+ def __build_child__(self, children: list):
135
+ html_parts = []
136
+ css_parts = []
137
+ for child in children:
138
+ if isinstance(child, str):
139
+ html_parts.append(f"{child}")
140
+ elif isinstance(child, list):
141
+ html, css = self.__build_child__(child)
142
+ html_parts.append(html)
143
+ css_parts.append(css)
144
+ elif isinstance(child, Component):
145
+ html, css = child.build()
146
+ html_parts.append(html)
147
+ css_parts.append(css)
148
+ # elif isinstance(child, JSDO):
149
+ # print(f"JSCode: {child().raw}")
150
+ # html_parts.append(f"{{{{{child().raw}}}}}")
151
+ elif isinstance(child, JSCode):
152
+ # JSCode outside of an attribute should be a special tag {{jscode_name}}
153
+ print(f"JSCode: {child.raw}")
154
+ html_parts.append(f"{{{{{child.raw}}}}}")
155
+ else:
156
+ try:
157
+ html_parts.append(str(child))
158
+ except Exception:
159
+ continue
160
+ return "".join(html_parts), "".join(filter(None, css_parts))
161
+
162
+ def build(self) -> tuple[str, str]:
163
+ """
164
+ Builds the component's HTML and CSS separately.
165
+
166
+ :return: A tuple (HTML string, CSS string)
167
+ """
168
+
169
+ if self.styles is not None and "id" not in self.attributes:
170
+ self.attr(id=self._id)
171
+ if len(self.children) == 0:
172
+ result = f"<{self.name}{self.__build_attr__()}/>\n"
173
+ else:
174
+ endln = "\n" if len(self.children) > 1 else ""
175
+ result = f"<{self.name}{self.__build_attr__()}>{endln}"
176
+ html, styles = self.__build_child__(self.children)
177
+ result += html
178
+ if self.styles is None:
179
+ self.styles = styles
180
+ else:
181
+ self.styles += styles
182
+ result += f"\t</{self.name}>\n"
183
+ css: str = "" if self.styles is None else self.styles
184
+ return result, css
185
+
186
+ def render(self) -> str:
187
+ """
188
+ Builds and returns the full HTML including inline CSS inside a <style> tag.
189
+
190
+ :return: A complete HTML string with embedded CSS.
191
+ """
192
+
193
+ if len(self.children) == 0:
194
+ result = f"<{self.name}{self.__build_attr__()}/>\n"
195
+ else:
196
+ inner_result, css = self.__build_child__(self.children)
197
+ if self.styles is None:
198
+ self.styles = css
199
+ else:
200
+ self.styles += css
201
+ self.styles += "\n"
202
+ result = f"<{self.name}{self.__build_attr__()}>\n"
203
+ if self.styles is not None:
204
+ result += f"<style>{self.styles}</style>\n"
205
+ result += inner_result
206
+ result += f"</{self.name}>\n"
207
+
208
+ return result
209
+
210
+
211
+ class ComponentMeta(type):
212
+ def __new__(cls, name, bases, dct):
213
+ if name not in ("Component", "ComponentMeta"):
214
+
215
+ def init(self, *childs):
216
+ super(self.__class__, self).__init__(name, *childs)
217
+
218
+ dct["__init__"] = init
219
+ return super().__new__(cls, name, bases, dct)
@@ -0,0 +1,137 @@
1
+ from types import FunctionType
2
+ from typing import Any, List, Union
3
+
4
+ from HTeaLeaf.Magic.jslib import JSCode, JSFunction, js
5
+
6
+ from .Component import Component, ComponentMeta
7
+
8
+
9
+ class html(Component, metaclass=ComponentMeta):
10
+ pass
11
+
12
+ class head(Component, metaclass=ComponentMeta):
13
+ pass
14
+
15
+ class header(Component, metaclass=ComponentMeta):
16
+ pass
17
+
18
+ class link(Component, metaclass=ComponentMeta):
19
+ def __init__(self, *childs):
20
+ super().__init__("link", *childs)
21
+
22
+
23
+ class script(Component):
24
+ def __init__(self, *childs: Union[str, List[Any], "Component", JSFunction] ,src=None):
25
+ parsed_childs: Union[str, List[Any], "Component"] = []
26
+ for child in childs:
27
+ if type(child) is FunctionType:
28
+ parsed_childs.append(js(child))
29
+ elif type(child) is JSFunction:
30
+ parsed_childs.append(child)
31
+ else:
32
+ parsed_childs.append(child)
33
+ super().__init__("script", *parsed_childs)
34
+ self.unsafe = True
35
+ if src is not None:
36
+ self.attr(src=src)
37
+ self.children = [""]
38
+ # else:
39
+
40
+
41
+
42
+ class style(Component, metaclass=ComponentMeta):
43
+ pass
44
+
45
+
46
+ class body(Component, metaclass=ComponentMeta):
47
+ pass
48
+
49
+
50
+ class h1(Component, metaclass=ComponentMeta):
51
+ pass
52
+
53
+
54
+ class h2(Component, metaclass=ComponentMeta):
55
+ pass
56
+
57
+
58
+ class h3(Component, metaclass=ComponentMeta):
59
+ pass
60
+
61
+
62
+ class div(Component, metaclass=ComponentMeta):
63
+ pass
64
+
65
+ def row(self):
66
+ self.attr(style="display: flex; flex-direction: row")
67
+ return self
68
+
69
+ def column(self):
70
+ self.attr(style="display: flex; flex-direction: column")
71
+ return self
72
+
73
+
74
+ class button(Component, metaclass=ComponentMeta):
75
+
76
+ def reactive(self,path,component_id):
77
+ """
78
+ Makes the button reactive by linking it to a FetchComponent.
79
+
80
+ :param path: The URL to fetch new data from when clicked.
81
+ :param component: The FetchComponent to be updated.
82
+ """
83
+
84
+ js = f"""fetchAndUpdate('{path}','{{}}','{component_id}')"""
85
+ self.attr(onclick=js)
86
+ return self
87
+
88
+
89
+
90
+ class label(Component, metaclass=ComponentMeta):
91
+ pass
92
+
93
+ class checkbox(Component):
94
+ def __init__(self,checked = False, *childs):
95
+ super().__init__("input", *childs)
96
+ self.attr(type="checkbox")
97
+ if checked:
98
+ self.attr(checked="True")
99
+
100
+ class textInput(Component):
101
+ def __init__(self, *childs):
102
+ super().__init__("input", *childs)
103
+
104
+ class select(Component):
105
+ def __init__(self, items: List[str]):
106
+ super().__init__("select")
107
+ for item in items:
108
+ self.append(option(item))
109
+
110
+ class option(Component):
111
+ def __init__(self, value):
112
+ super().__init__("option", value)
113
+ self.attr(value=value)
114
+
115
+
116
+ class submit(Component):
117
+ def __init__(self, *childs):
118
+ super().__init__("input", *childs)
119
+ self.attr(type="submit")
120
+
121
+ class form(Component):
122
+ def __init__(self, *childs):
123
+ super().__init__("form", *childs)
124
+
125
+ def action(self, action):
126
+ self.attr(action=action)
127
+ return self
128
+
129
+ def method(self, method):
130
+ self.attr(method=method)
131
+ return self
132
+
133
+ def tl_if(condition: JSCode | str | bool, *childs):
134
+ if isinstance(condition, str):
135
+ condition = JSCode(condition)
136
+
137
+ return div(*childs).attr(style=f"display: {condition};")
@@ -0,0 +1,2 @@
1
+ # from .Elements import *
2
+ # from .Component import Component
@@ -0,0 +1,13 @@
1
+ from ..Html.Component import Component
2
+ from ..Html.Elements import script
3
+ from ..Server.Server import Server, ServerEvent
4
+
5
+
6
+ def enable_reactivity(server: Server):
7
+ helper_script = script(src="_engine/helper.js")
8
+
9
+ def event_handler(res_code, res_body, res_headers):
10
+ if isinstance(res_body, Component):
11
+ res_body.prepend(helper_script)
12
+
13
+ server.registry_hook(ServerEvent.on_response, event_handler)
@@ -0,0 +1,38 @@
1
+ import hashlib
2
+ import inspect
3
+ import json
4
+
5
+ from HTeaLeaf.Html.Elements import script
6
+
7
+ from ..Magic.jslib.JSCode import JSCode
8
+ from ..Magic.jslib.JSDO import JSDO
9
+
10
+ # class localState():
11
+ # def __init__(self, init_state):
12
+ # self.do = JSDO("LocalState",init_state)
13
+
14
+ # def
15
+
16
+ # def js(self):
17
+ # return self.do.js()
18
+
19
+ # def get(self):
20
+ # return self.do.get()
21
+
22
+ # def set(self, data):
23
+ # return self.do.set(data)
24
+
25
+
26
+
27
+ def use_state(init_state):
28
+ frame = inspect.stack()[1]
29
+ site = f"{frame.filename}:{frame.lineno}"
30
+ raw = f"{site}:{json.dumps(init_state, sort_keys=True)}"
31
+ id = hashlib.md5(raw.encode()).hexdigest()[:12]
32
+ name = f"localstate_{id}"
33
+
34
+ def new():
35
+ return script(f"const {name} = new LocalState({json.dumps(init_state, sort_keys=True)},\"{name}\");")
36
+
37
+
38
+ return new, JSCode(name)
@@ -0,0 +1,122 @@
1
+ import json
2
+ import os
3
+ import uuid
4
+ from typing import Any
5
+
6
+ from ..Html.Component import Component
7
+ from ..Html.Elements import div, script
8
+
9
+
10
+ class FetchComponent(Component):
11
+ """
12
+ A component that fetches data from a given URL and updates its content dynamically.
13
+ """
14
+
15
+ def __init__(self, url, body: str | dict | None = None) -> None:
16
+ """
17
+ Initializes a FetchComponent that loads content asynchronously.
18
+
19
+ :param url: The URL to fetch data from.
20
+ :param body: Optional request body for POST requests.
21
+ """
22
+
23
+ self._reid = new_id()
24
+ placeholder = div("Loading...").id(self._reid)
25
+ super().__init__("div", placeholder)
26
+ # Configuración de la petición
27
+ config = {"method": "POST" if body is not None else "GET", "body": body }
28
+ # Serializar la configuración en JSON para JS
29
+ _js_file = os.path.dirname(__file__) + "/MagicComponent.js"
30
+ url = json.dumps(url)
31
+ config_js = json.dumps(config)
32
+ _id = json.dumps(placeholder._id)
33
+ js: str = f"fetchAndUpdate({url},{config_js},{placeholder._id})"
34
+
35
+ self.append(script(js))
36
+
37
+ def reid(self):
38
+ """
39
+ Returns the reactive ID of the component.
40
+ """
41
+
42
+ return self._reid
43
+
44
+ class rButton(Component):
45
+ """
46
+ A reactive button that triggers updates on FetchComponents.
47
+ """
48
+
49
+ def __init__(self, *childs):
50
+ """
51
+ Initializes an rButton.
52
+
53
+ :param childs: The child elements (text or components) inside the button.
54
+ """
55
+ super().__init__("button", *childs)
56
+
57
+ def reactive(self,path,component: FetchComponent):
58
+ """
59
+ Makes the button reactive by linking it to a FetchComponent.
60
+
61
+ :param path: The URL to fetch new data from when clicked.
62
+ :param component: The FetchComponent to be updated.
63
+ """
64
+
65
+ #config = {"method": "GET"}
66
+ if not hasattr(component, "reid"):
67
+ raise Exception("component is not reactive")
68
+ id = component.reid()
69
+ # Serializar la configuración en JSON para JS
70
+ #config_js = json.dumps(config)
71
+ js = f"""fetchAndUpdate('{path}','{{}}','{id}')"""
72
+ self.attr(onclick=js)
73
+ return self
74
+
75
+ #def refresh(self, path)
76
+
77
+ class HydratedComponent(Component):
78
+ """
79
+ WIP🚧
80
+ A component that supports server-side hydration for dynamic updates.
81
+ """
82
+
83
+ def __init__(self, *childs):
84
+ """
85
+ Initializes a HydratedComponent.
86
+
87
+ :param childs: The child elements inside the component.
88
+ """
89
+
90
+ pass
91
+
92
+
93
+ class PoolComponent(Component):
94
+ """
95
+ WIP🚧
96
+ A component that periodically fetches data from a URL and updates itself.
97
+ """
98
+
99
+ def __init__(self, child: Component, url: str, interval: int = 5000):
100
+ """
101
+ Initializes a PoolComponent.
102
+
103
+ :param child: The component to be updated.
104
+ :param url: The URL from which new data will be fetched.
105
+ :param interval: The time interval (in milliseconds) for polling updates. Default is 5000ms.
106
+ """
107
+ super().__init__("div", child)
108
+ js = f"""
109
+ setInterval(() => fetchAndUpdate('{url}', '{{}}', '{child._id}'), {interval});
110
+ """
111
+ self.append(script(js))
112
+
113
+
114
+ def new_id():
115
+ """
116
+ Generates a new unique identifier for reactive components.
117
+ """
118
+ return "tlmg" + str(uuid.uuid4()).split("-")[0]
119
+
120
+
121
+ def to_js_type(data: Any) -> str:
122
+ return json.dumps(data)