dars-framework 1.2.3__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.
- dars/__init__.py +0 -0
- dars/all.py +69 -0
- dars/cli/__init__.py +0 -0
- dars/cli/doctor/__init__.py +1 -0
- dars/cli/doctor/detect.py +154 -0
- dars/cli/doctor/doctor.py +176 -0
- dars/cli/doctor/installers.py +100 -0
- dars/cli/doctor/persist.py +62 -0
- dars/cli/doctor/preflight.py +33 -0
- dars/cli/doctor/ui.py +54 -0
- dars/cli/hot_reload.py +33 -0
- dars/cli/main.py +1107 -0
- dars/cli/preview.py +448 -0
- dars/cli/translations.py +531 -0
- dars/components/__init__.py +0 -0
- dars/components/advanced/__init__.py +8 -0
- dars/components/advanced/accordion.py +26 -0
- dars/components/advanced/card.py +33 -0
- dars/components/advanced/modal.py +45 -0
- dars/components/advanced/navbar.py +44 -0
- dars/components/advanced/table.py +25 -0
- dars/components/advanced/tabs.py +31 -0
- dars/components/basic/__init__.py +34 -0
- dars/components/basic/button.py +55 -0
- dars/components/basic/checkbox.py +35 -0
- dars/components/basic/container.py +29 -0
- dars/components/basic/datepicker.py +139 -0
- dars/components/basic/image.py +36 -0
- dars/components/basic/input.py +57 -0
- dars/components/basic/link.py +31 -0
- dars/components/basic/markdown.py +86 -0
- dars/components/basic/page.py +20 -0
- dars/components/basic/progressbar.py +18 -0
- dars/components/basic/radiobutton.py +35 -0
- dars/components/basic/select.py +82 -0
- dars/components/basic/slider.py +63 -0
- dars/components/basic/spinner.py +12 -0
- dars/components/basic/text.py +23 -0
- dars/components/basic/textarea.py +46 -0
- dars/components/basic/tooltip.py +19 -0
- dars/components/layout/__init__.py +0 -0
- dars/components/layout/anchor.py +13 -0
- dars/components/layout/flex.py +26 -0
- dars/components/layout/grid.py +45 -0
- dars/config.py +134 -0
- dars/core/__init__.py +0 -0
- dars/core/app.py +957 -0
- dars/core/component.py +284 -0
- dars/core/events.py +102 -0
- dars/core/js_bridge.py +99 -0
- dars/core/properties.py +127 -0
- dars/core/state.py +309 -0
- dars/dars_tests/apps_test/health_check.py +56 -0
- dars/dars_tests/run_tests.py +275 -0
- dars/dars_tests/tests/test_advanced_components.py +69 -0
- dars/dars_tests/tests/test_basic_components.py +88 -0
- dars/dars_tests/tests/test_core_and_cli.py +17 -0
- dars/dars_tests/tests/test_layout_components.py +58 -0
- dars/dars_tests/tests/test_version_check.py +21 -0
- dars/docs/__init__.py +0 -0
- dars/docs/app.md +290 -0
- dars/docs/cli.md +80 -0
- dars/docs/components.md +1679 -0
- dars/docs/custom_components.md +30 -0
- dars/docs/events.md +45 -0
- dars/docs/exporters.md +162 -0
- dars/docs/getting_started.md +79 -0
- dars/docs/index.md +18 -0
- dars/docs/scripts.md +593 -0
- dars/docs/state_management.md +57 -0
- dars/exporters/__init__.py +0 -0
- dars/exporters/base.py +96 -0
- dars/exporters/web/OLD/html_css_js_OLD4.py +1538 -0
- dars/exporters/web/OLD/html_css_js_old.py +1406 -0
- dars/exporters/web/OLD/html_css_js_old2.py +1406 -0
- dars/exporters/web/__init__.py +0 -0
- dars/exporters/web/html_css_js.py +2675 -0
- dars/exporters/web/vdom.py +251 -0
- dars/js_lib.py +206 -0
- dars/scripts/__init__.py +0 -0
- dars/scripts/dscript.py +26 -0
- dars/scripts/script.py +39 -0
- dars/security.py +195 -0
- dars/templates/__init__.py +0 -0
- dars/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- dars/templates/examples/README.md +4 -0
- dars/templates/examples/__pycache__/dynamic_event_demo.cpython-311.pyc +0 -0
- dars/templates/examples/advanced/Modal_Demo/advanced_modal_demo.py +275 -0
- dars/templates/examples/advanced/SimpleDashboard/dashboard.py +437 -0
- dars/templates/examples/advanced/SimpleModermWeb/modern_web_app.py +452 -0
- dars/templates/examples/advanced/VariousComponents/all_components_demo.py +87 -0
- dars/templates/examples/advanced/__init__.py +0 -0
- dars/templates/examples/advanced/dState/state_mods_demo.py +68 -0
- dars/templates/examples/basic/Forms/form_components.py +516 -0
- dars/templates/examples/basic/Forms/simple_form.py +379 -0
- dars/templates/examples/basic/HelloWorld/hello_world.py +56 -0
- dars/templates/examples/basic/Layouts/flex_layout_responsive.py +13 -0
- dars/templates/examples/basic/Layouts/grid_layout_responsive.py +12 -0
- dars/templates/examples/basic/Layouts/layout_multipage_demo.py +23 -0
- dars/templates/examples/basic/Multipage/multipage_example.py +67 -0
- dars/templates/examples/basic/PWA/icon-192x192.png +0 -0
- dars/templates/examples/basic/PWA/icon-512x512.png +0 -0
- dars/templates/examples/basic/PWA/pwa_custom_icons.py +33 -0
- dars/templates/examples/basic/__init__.py +0 -0
- dars/templates/examples/demo/__pycache__/complete_app.cpython-311.pyc +0 -0
- dars/templates/examples/demo/complete_app.py +21 -0
- dars/templates/examples/markdown/MarkdownTemplate/README.md +159 -0
- dars/templates/examples/markdown/MarkdownTemplate/markdown_template.py +21 -0
- dars/templates/examples/markdown/MarkdownTemplate/other_docs.md +1 -0
- dars/templates/examples/markdown/__init__.py +0 -0
- dars/templates/html/__init__.py +0 -0
- dars/version.py +2 -0
- dars_framework-1.2.3.dist-info/METADATA +15 -0
- dars_framework-1.2.3.dist-info/RECORD +118 -0
- dars_framework-1.2.3.dist-info/WHEEL +5 -0
- dars_framework-1.2.3.dist-info/entry_points.txt +2 -0
- dars_framework-1.2.3.dist-info/licenses/LICENSE +21 -0
- dars_framework-1.2.3.dist-info/top_level.txt +1 -0
dars/core/component.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Optional, Callable, Union, Type
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
|
|
5
|
+
class ComponentQuery:
|
|
6
|
+
def __init__(self, components: List['Component']):
|
|
7
|
+
self.components = components
|
|
8
|
+
|
|
9
|
+
def find(self,
|
|
10
|
+
id: Optional[str] = None,
|
|
11
|
+
class_name: Optional[str] = None,
|
|
12
|
+
type: Optional[Union[Type['Component'], str]] = None,
|
|
13
|
+
predicate: Optional[Callable[['Component'], bool]] = None) -> 'ComponentQuery':
|
|
14
|
+
|
|
15
|
+
"""Searches for components within the currently selected components."""
|
|
16
|
+
results: List['Component'] = []
|
|
17
|
+
|
|
18
|
+
def match_component(comp: Component) -> bool:
|
|
19
|
+
if id is not None and comp.id != id:
|
|
20
|
+
return False
|
|
21
|
+
if class_name is not None and comp.class_name != class_name:
|
|
22
|
+
return False
|
|
23
|
+
if type is not None:
|
|
24
|
+
if isinstance(type, str):
|
|
25
|
+
if comp.__class__.__name__ != type:
|
|
26
|
+
return False
|
|
27
|
+
elif not isinstance(comp, type):
|
|
28
|
+
return False
|
|
29
|
+
if predicate is not None and not predicate(comp):
|
|
30
|
+
return False
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
# Buscar en los hijos de todos los componentes actuales
|
|
34
|
+
for component in self.components:
|
|
35
|
+
for child in component.children:
|
|
36
|
+
if match_component(child):
|
|
37
|
+
results.append(child)
|
|
38
|
+
# Buscar recursivamente en los hijos del hijo
|
|
39
|
+
for descendant in ComponentQuery([child]).find().get():
|
|
40
|
+
if match_component(descendant):
|
|
41
|
+
results.append(descendant)
|
|
42
|
+
|
|
43
|
+
return ComponentQuery(results)
|
|
44
|
+
|
|
45
|
+
def attr(self, **attrs) -> 'ComponentQuery':
|
|
46
|
+
"""Modifies the attributes of all found components."""
|
|
47
|
+
for component in self.components:
|
|
48
|
+
for key, value in attrs.items():
|
|
49
|
+
# Manejo especial para atributos comunes
|
|
50
|
+
if key == 'style':
|
|
51
|
+
component.style.update(value)
|
|
52
|
+
continue
|
|
53
|
+
elif key == 'class_name':
|
|
54
|
+
component.class_name = value
|
|
55
|
+
continue
|
|
56
|
+
elif key == 'events':
|
|
57
|
+
component.events.update(value)
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
# Intenta establecer el atributo directamente si existe
|
|
61
|
+
if hasattr(component, key):
|
|
62
|
+
setattr(component, key, value)
|
|
63
|
+
# Si no existe como atributo directo, guárdalo en props
|
|
64
|
+
else:
|
|
65
|
+
component.props[key] = value
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def get(self) -> List['Component']:
|
|
69
|
+
"""Returns the list of found components."""
|
|
70
|
+
return self.components
|
|
71
|
+
|
|
72
|
+
def first(self) -> Optional['Component']:
|
|
73
|
+
"""Returns the first found component, or None if there is none."""
|
|
74
|
+
return self.components[0] if self.components else None
|
|
75
|
+
|
|
76
|
+
class Component(ABC):
|
|
77
|
+
def __init__(self, **props):
|
|
78
|
+
self.props = props
|
|
79
|
+
self.children: List[Component] = []
|
|
80
|
+
self.parent: Optional[Component] = None
|
|
81
|
+
self.id: Optional[str] = props.get('id')
|
|
82
|
+
self.class_name: str = props.get("class_name", self.__class__.__name__)
|
|
83
|
+
self.style: Dict[str, Any] = props.get('style', {})
|
|
84
|
+
self.events: Dict[str, Callable] = {}
|
|
85
|
+
# Stable identity hint for children reconciliation in VDOM (optional)
|
|
86
|
+
self.key: Optional[str] = props.get('key')
|
|
87
|
+
|
|
88
|
+
# Generic on_* props -> register as events on any component
|
|
89
|
+
if props:
|
|
90
|
+
on_map = {
|
|
91
|
+
'on_click': EventTypes.CLICK,
|
|
92
|
+
'on_double_click': EventTypes.DOUBLE_CLICK,
|
|
93
|
+
'on_mouse_down': EventTypes.MOUSE_DOWN,
|
|
94
|
+
'on_mouse_up': EventTypes.MOUSE_UP,
|
|
95
|
+
'on_mouse_enter': EventTypes.MOUSE_ENTER,
|
|
96
|
+
'on_mouse_leave': EventTypes.MOUSE_LEAVE,
|
|
97
|
+
'on_mouse_move': EventTypes.MOUSE_MOVE,
|
|
98
|
+
'on_key_down': EventTypes.KEY_DOWN,
|
|
99
|
+
'on_key_up': EventTypes.KEY_UP,
|
|
100
|
+
'on_key_press': EventTypes.KEY_PRESS,
|
|
101
|
+
'on_change': EventTypes.CHANGE,
|
|
102
|
+
'on_input': EventTypes.INPUT,
|
|
103
|
+
'on_submit': EventTypes.SUBMIT,
|
|
104
|
+
'on_focus': EventTypes.FOCUS,
|
|
105
|
+
'on_blur': EventTypes.BLUR,
|
|
106
|
+
'on_load': EventTypes.LOAD,
|
|
107
|
+
'on_error': EventTypes.ERROR,
|
|
108
|
+
'on_resize': EventTypes.RESIZE,
|
|
109
|
+
}
|
|
110
|
+
for k, v in list(props.items()):
|
|
111
|
+
if k in on_map and v is not None:
|
|
112
|
+
handler = v
|
|
113
|
+
# Normalize handler to Script-like if possible
|
|
114
|
+
try:
|
|
115
|
+
from dars.scripts.script import Script
|
|
116
|
+
if not isinstance(handler, Script):
|
|
117
|
+
if callable(handler):
|
|
118
|
+
from dars.scripts.dscript import dScript
|
|
119
|
+
handler = dScript(handler.__code__)
|
|
120
|
+
except Exception:
|
|
121
|
+
# Best-effort: keep as-is (string or callable)
|
|
122
|
+
pass
|
|
123
|
+
self.set_event(on_map[k], handler)
|
|
124
|
+
|
|
125
|
+
def add_child(self, child: 'Component'):
|
|
126
|
+
if isinstance(child, type) and issubclass(child, Component):
|
|
127
|
+
raise TypeError(f"The class {child.__name__} was passed instead of an instance. You should use {child.__name__}(...).")
|
|
128
|
+
child.parent = self
|
|
129
|
+
self.children.append(child)
|
|
130
|
+
|
|
131
|
+
def set_event(self, event_name: str, handler: Callable):
|
|
132
|
+
self.events[event_name] = handler
|
|
133
|
+
|
|
134
|
+
def find(self,
|
|
135
|
+
id: Optional[str] = None,
|
|
136
|
+
class_name: Optional[str] = None,
|
|
137
|
+
type: Optional[Union[Type['Component'], str]] = None,
|
|
138
|
+
predicate: Optional[Callable[['Component'], bool]] = None) -> ComponentQuery:
|
|
139
|
+
"""Searches for components that match the specified criteria.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
id: Search by component ID
|
|
143
|
+
class_name: Search by CSS class name
|
|
144
|
+
type: Search by component type (class or class name)
|
|
145
|
+
predicate: Custom filter function that takes a component and returns bool
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
ComponentQuery that allows chaining operations and modifying attributes
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
results: List[Component] = []
|
|
152
|
+
|
|
153
|
+
def match_component(comp: Component) -> bool:
|
|
154
|
+
if id is not None and comp.id != id:
|
|
155
|
+
return False
|
|
156
|
+
if class_name is not None and comp.class_name != class_name:
|
|
157
|
+
return False
|
|
158
|
+
if type is not None:
|
|
159
|
+
if isinstance(type, str):
|
|
160
|
+
if comp.__class__.__name__ != type:
|
|
161
|
+
return False
|
|
162
|
+
elif not isinstance(comp, type):
|
|
163
|
+
return False
|
|
164
|
+
if predicate is not None and not predicate(comp):
|
|
165
|
+
return False
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
def search_recursive(component: Component):
|
|
169
|
+
if match_component(component):
|
|
170
|
+
results.append(component)
|
|
171
|
+
for child in component.children:
|
|
172
|
+
search_recursive(child)
|
|
173
|
+
|
|
174
|
+
search_recursive(self)
|
|
175
|
+
return ComponentQuery(results)
|
|
176
|
+
|
|
177
|
+
def attr(self, **attrs) -> Union['Component', dict]:
|
|
178
|
+
"""If kwargs are provided, sets attributes on the component (chained setter).
|
|
179
|
+
If no kwargs are provided, returns a dict with all editable component attributes (getter).
|
|
180
|
+
Example:
|
|
181
|
+
c.attr(id='new', style={'color': 'red'})
|
|
182
|
+
c.attr()['id'] # getter
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
if attrs:
|
|
186
|
+
if 'defer' in attrs:
|
|
187
|
+
try:
|
|
188
|
+
d = attrs.pop('defer')
|
|
189
|
+
if d:
|
|
190
|
+
return DeferredAttr(self, attrs)
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
for key, value in attrs.items():
|
|
194
|
+
if key == 'style':
|
|
195
|
+
self.style.update(value)
|
|
196
|
+
continue
|
|
197
|
+
elif key == 'class_name':
|
|
198
|
+
self.class_name = value
|
|
199
|
+
continue
|
|
200
|
+
elif key == 'events':
|
|
201
|
+
self.events.update(value)
|
|
202
|
+
continue
|
|
203
|
+
# Allow setting on_* event properties via attr()
|
|
204
|
+
if key.startswith('on_') and value is not None:
|
|
205
|
+
try:
|
|
206
|
+
event_name = {
|
|
207
|
+
'on_click': EventTypes.CLICK,
|
|
208
|
+
'on_double_click': EventTypes.DOUBLE_CLICK,
|
|
209
|
+
'on_mouse_down': EventTypes.MOUSE_DOWN,
|
|
210
|
+
'on_mouse_up': EventTypes.MOUSE_UP,
|
|
211
|
+
'on_mouse_enter': EventTypes.MOUSE_ENTER,
|
|
212
|
+
'on_mouse_leave': EventTypes.MOUSE_LEAVE,
|
|
213
|
+
'on_mouse_move': EventTypes.MOUSE_MOVE,
|
|
214
|
+
'on_key_down': EventTypes.KEY_DOWN,
|
|
215
|
+
'on_key_up': EventTypes.KEY_UP,
|
|
216
|
+
'on_key_press': EventTypes.KEY_PRESS,
|
|
217
|
+
'on_change': EventTypes.CHANGE,
|
|
218
|
+
'on_input': EventTypes.INPUT,
|
|
219
|
+
'on_submit': EventTypes.SUBMIT,
|
|
220
|
+
'on_focus': EventTypes.FOCUS,
|
|
221
|
+
'on_blur': EventTypes.BLUR,
|
|
222
|
+
'on_load': EventTypes.LOAD,
|
|
223
|
+
'on_error': EventTypes.ERROR,
|
|
224
|
+
'on_resize': EventTypes.RESIZE,
|
|
225
|
+
}.get(key)
|
|
226
|
+
if event_name:
|
|
227
|
+
handler = value
|
|
228
|
+
from dars.scripts.script import Script
|
|
229
|
+
if not isinstance(handler, Script):
|
|
230
|
+
if callable(handler):
|
|
231
|
+
from dars.scripts.dscript import dScript
|
|
232
|
+
handler = dScript(handler.__code__)
|
|
233
|
+
self.set_event(event_name, handler)
|
|
234
|
+
continue
|
|
235
|
+
except Exception:
|
|
236
|
+
# If any error, fall back to setting as a prop
|
|
237
|
+
pass
|
|
238
|
+
if hasattr(self, key):
|
|
239
|
+
setattr(self, key, value)
|
|
240
|
+
else:
|
|
241
|
+
self.props[key] = value
|
|
242
|
+
return self
|
|
243
|
+
# Getter: devolver todos los atributos editables
|
|
244
|
+
d = dict(self.props)
|
|
245
|
+
d['id'] = self.id
|
|
246
|
+
d['class_name'] = self.class_name
|
|
247
|
+
d['style'] = self.style
|
|
248
|
+
d['events'] = self.events
|
|
249
|
+
return d
|
|
250
|
+
|
|
251
|
+
def mod(self, **attrs):
|
|
252
|
+
return DeferredAttr(self, attrs)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class DeferredAttr:
|
|
256
|
+
def __init__(self, component: 'Component', attrs: Dict[str, Any]):
|
|
257
|
+
self.component = component
|
|
258
|
+
self.attrs = attrs or {}
|
|
259
|
+
|
|
260
|
+
def clone_with(self) -> 'Component':
|
|
261
|
+
try:
|
|
262
|
+
import copy
|
|
263
|
+
clone = copy.copy(self.component)
|
|
264
|
+
except Exception:
|
|
265
|
+
clone = self.component
|
|
266
|
+
try:
|
|
267
|
+
if hasattr(clone, 'attr') and callable(getattr(clone, 'attr')):
|
|
268
|
+
clone.attr(**self.attrs)
|
|
269
|
+
except Exception:
|
|
270
|
+
pass
|
|
271
|
+
return clone
|
|
272
|
+
|
|
273
|
+
def render_children(self, exporter: 'Exporter') -> str:
|
|
274
|
+
"""Render all children of the component using the exporter."""
|
|
275
|
+
children_html = ""
|
|
276
|
+
for child in self.children:
|
|
277
|
+
children_html += exporter.render_component(child)
|
|
278
|
+
return children_html
|
|
279
|
+
|
|
280
|
+
@abstractmethod
|
|
281
|
+
def render(self, exporter: 'Exporter') -> str:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
|
dars/core/events.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from typing import Callable, Dict, Any, Optional
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
class EventHandler:
|
|
5
|
+
"""Event handler for components"""
|
|
6
|
+
|
|
7
|
+
def __init__(self, handler: Callable, event_type: str):
|
|
8
|
+
self.handler = handler
|
|
9
|
+
self.event_type = event_type
|
|
10
|
+
self.id = f"event_{id(self)}"
|
|
11
|
+
|
|
12
|
+
def __call__(self, *args, **kwargs):
|
|
13
|
+
return self.handler(*args, **kwargs)
|
|
14
|
+
|
|
15
|
+
class EventManager:
|
|
16
|
+
"""Event manager for the application"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.handlers: Dict[str, EventHandler] = {}
|
|
20
|
+
self.component_events: Dict[str, Dict[str, EventHandler]] = {}
|
|
21
|
+
|
|
22
|
+
def register_event(self, component_id: str, event_type: str, handler: Callable) -> str:
|
|
23
|
+
"""Registers an event for a component"""
|
|
24
|
+
event_handler = EventHandler(handler, event_type)
|
|
25
|
+
|
|
26
|
+
if component_id not in self.component_events:
|
|
27
|
+
self.component_events[component_id] = {}
|
|
28
|
+
|
|
29
|
+
self.component_events[component_id][event_type] = event_handler
|
|
30
|
+
self.handlers[event_handler.id] = event_handler
|
|
31
|
+
|
|
32
|
+
return event_handler.id
|
|
33
|
+
|
|
34
|
+
def get_event_handler(self, handler_id: str) -> Optional[EventHandler]:
|
|
35
|
+
"""Gets all events of a component by ID"""
|
|
36
|
+
|
|
37
|
+
return self.handlers.get(handler_id)
|
|
38
|
+
|
|
39
|
+
def get_component_events(self, component_id: str) -> Dict[str, EventHandler]:
|
|
40
|
+
"""Gets all events of a component"""
|
|
41
|
+
return self.component_events.get(component_id, {})
|
|
42
|
+
|
|
43
|
+
def remove_event(self, component_id: str, event_type: str):
|
|
44
|
+
"""Removes an event from a component"""
|
|
45
|
+
if component_id in self.component_events:
|
|
46
|
+
if event_type in self.component_events[component_id]:
|
|
47
|
+
handler = self.component_events[component_id][event_type]
|
|
48
|
+
del self.handlers[handler.id]
|
|
49
|
+
del self.component_events[component_id][event_type]
|
|
50
|
+
|
|
51
|
+
class EventEmitter(ABC):
|
|
52
|
+
"""Base class for components that can emit events"""
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self.event_manager = EventManager()
|
|
56
|
+
|
|
57
|
+
def on(self, event_type: str, handler: Callable) -> str:
|
|
58
|
+
"""Registers an event handler"""
|
|
59
|
+
component_id = getattr(self, 'id', str(id(self)))
|
|
60
|
+
return self.event_manager.register_event(component_id, event_type, handler)
|
|
61
|
+
|
|
62
|
+
def off(self, event_type: str):
|
|
63
|
+
"""Removes an event handler"""
|
|
64
|
+
component_id = getattr(self, 'id', str(id(self)))
|
|
65
|
+
self.event_manager.remove_event(component_id, event_type)
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def emit(self, event_type: str, *args, **kwargs):
|
|
69
|
+
"""Emits an event"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
# Tipos de eventos estándar
|
|
73
|
+
class EventTypes:
|
|
74
|
+
# Eventos de mouse
|
|
75
|
+
CLICK = "click"
|
|
76
|
+
DOUBLE_CLICK = "dblclick"
|
|
77
|
+
MOUSE_DOWN = "mousedown"
|
|
78
|
+
MOUSE_UP = "mouseup"
|
|
79
|
+
MOUSE_ENTER = "mouseenter"
|
|
80
|
+
MOUSE_LEAVE = "mouseleave"
|
|
81
|
+
MOUSE_MOVE = "mousemove"
|
|
82
|
+
|
|
83
|
+
# Eventos de teclado
|
|
84
|
+
KEY_DOWN = "keydown"
|
|
85
|
+
KEY_UP = "keyup"
|
|
86
|
+
KEY_PRESS = "keypress"
|
|
87
|
+
|
|
88
|
+
# Eventos de formulario
|
|
89
|
+
CHANGE = "change"
|
|
90
|
+
INPUT = "input"
|
|
91
|
+
SUBMIT = "submit"
|
|
92
|
+
FOCUS = "focus"
|
|
93
|
+
BLUR = "blur"
|
|
94
|
+
|
|
95
|
+
# Eventos de carga
|
|
96
|
+
LOAD = "load"
|
|
97
|
+
ERROR = "error"
|
|
98
|
+
RESIZE = "resize"
|
|
99
|
+
|
|
100
|
+
# Eventos personalizados
|
|
101
|
+
CUSTOM = "custom"
|
|
102
|
+
|
dars/core/js_bridge.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import tempfile
|
|
4
|
+
from typing import List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _run(cmd: List[str], cwd: Optional[str] = None, live: bool = False) -> Tuple[int, str, str]:
|
|
8
|
+
try:
|
|
9
|
+
if live:
|
|
10
|
+
p = subprocess.run(cmd, cwd=cwd)
|
|
11
|
+
return (p.returncode or 0, "", "")
|
|
12
|
+
p = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
|
|
13
|
+
return p.returncode, (p.stdout or ""), (p.stderr or "")
|
|
14
|
+
except Exception as e:
|
|
15
|
+
return 1, "", str(e)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def which(name: str) -> Optional[str]:
|
|
19
|
+
cmd = ["where", name] if os.name == 'nt' else ["which", name]
|
|
20
|
+
code, out, _ = _run(cmd)
|
|
21
|
+
if code == 0 and out:
|
|
22
|
+
return out.strip().splitlines()[0]
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def has_node() -> bool:
|
|
27
|
+
return which("node") is not None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def has_bun() -> bool:
|
|
31
|
+
return which("bun") is not None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def bun_add(packages: List[str], dev: bool = True, cwd: Optional[str] = None) -> bool:
|
|
35
|
+
if not has_bun():
|
|
36
|
+
return False
|
|
37
|
+
args = ["bun", "add"]
|
|
38
|
+
if dev:
|
|
39
|
+
args.append("-d")
|
|
40
|
+
args.extend(packages)
|
|
41
|
+
code, _, _ = _run(args, cwd=cwd, live=True)
|
|
42
|
+
return code == 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def node_run(code: str) -> Tuple[int, str, str]:
|
|
46
|
+
if not has_node():
|
|
47
|
+
return 1, "", "node not found"
|
|
48
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mjs", mode="w", encoding="utf-8") as tf:
|
|
49
|
+
tf.write(code)
|
|
50
|
+
temp = tf.name
|
|
51
|
+
try:
|
|
52
|
+
return _run(["node", temp])
|
|
53
|
+
finally:
|
|
54
|
+
try:
|
|
55
|
+
os.remove(temp)
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def esbuild_available() -> bool:
|
|
61
|
+
# Prefer bun esbuild
|
|
62
|
+
if has_bun():
|
|
63
|
+
# bun x esbuild --version
|
|
64
|
+
code, out, _ = _run(["bun", "x", "esbuild", "--version"])
|
|
65
|
+
if code == 0:
|
|
66
|
+
return True
|
|
67
|
+
# Node npx fallback
|
|
68
|
+
if has_node():
|
|
69
|
+
code, out, _ = _run(["npx", "--yes", "esbuild", "--version"]) # --yes to avoid prompt
|
|
70
|
+
if code == 0:
|
|
71
|
+
return True
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def ensure_esbuild(cwd: Optional[str] = None) -> bool:
|
|
76
|
+
if esbuild_available():
|
|
77
|
+
return True
|
|
78
|
+
# Try to add via bun (dev dep)
|
|
79
|
+
if has_bun():
|
|
80
|
+
ok = bun_add(["esbuild"], dev=True, cwd=cwd)
|
|
81
|
+
return ok and esbuild_available()
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def esbuild_minify_js(src_path: str, out_path: Optional[str] = None) -> bool:
|
|
86
|
+
if not esbuild_available() and not ensure_esbuild(os.path.dirname(src_path)):
|
|
87
|
+
return False
|
|
88
|
+
out = out_path or src_path
|
|
89
|
+
if has_bun():
|
|
90
|
+
code, _, _ = _run(["bun", "x", "esbuild", src_path, "--minify", "--legal-comments=none", "--platform=browser", "--format=esm", f"--outfile={out}"])
|
|
91
|
+
return code == 0
|
|
92
|
+
# Node fallback via npx
|
|
93
|
+
code, _, _ = _run(["npx", "--yes", "esbuild", src_path, "--minify", "--legal-comments=none", "--platform=browser", "--format=esm", f"--outfile={out}"])
|
|
94
|
+
return code == 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def esbuild_minify_css(src_path: str, out_path: Optional[str] = None) -> bool:
|
|
98
|
+
# esbuild can minify CSS if input is CSS
|
|
99
|
+
return esbuild_minify_js(src_path, out_path)
|
dars/core/properties.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from typing import Union, Optional, Dict, Any, Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class StyleProps:
|
|
6
|
+
"""Style properties for UI components"""
|
|
7
|
+
# Dimensiones
|
|
8
|
+
width: Optional[Union[str, int]] = None
|
|
9
|
+
height: Optional[Union[str, int]] = None
|
|
10
|
+
min_width: Optional[Union[str, int]] = None
|
|
11
|
+
min_height: Optional[Union[str, int]] = None
|
|
12
|
+
max_width: Optional[Union[str, int]] = None
|
|
13
|
+
max_height: Optional[Union[str, int]] = None
|
|
14
|
+
|
|
15
|
+
# Espaciado
|
|
16
|
+
margin: Optional[Union[str, int]] = None
|
|
17
|
+
margin_top: Optional[Union[str, int]] = None
|
|
18
|
+
margin_right: Optional[Union[str, int]] = None
|
|
19
|
+
margin_bottom: Optional[Union[str, int]] = None
|
|
20
|
+
margin_left: Optional[Union[str, int]] = None
|
|
21
|
+
padding: Optional[Union[str, int]] = None
|
|
22
|
+
padding_top: Optional[Union[str, int]] = None
|
|
23
|
+
padding_right: Optional[Union[str, int]] = None
|
|
24
|
+
padding_bottom: Optional[Union[str, int]] = None
|
|
25
|
+
padding_left: Optional[Union[str, int]] = None
|
|
26
|
+
|
|
27
|
+
# Colores
|
|
28
|
+
background_color: Optional[str] = None
|
|
29
|
+
color: Optional[str] = None
|
|
30
|
+
border_color: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
# Tipografía
|
|
33
|
+
font_size: Optional[Union[str, int]] = None
|
|
34
|
+
font_family: Optional[str] = None
|
|
35
|
+
font_weight: Optional[Union[str, int]] = None
|
|
36
|
+
font_style: Optional[str] = None
|
|
37
|
+
text_align: Optional[str] = None
|
|
38
|
+
text_decoration: Optional[str] = None
|
|
39
|
+
line_height: Optional[Union[str, int]] = None
|
|
40
|
+
|
|
41
|
+
# Bordes
|
|
42
|
+
border: Optional[str] = None
|
|
43
|
+
border_width: Optional[Union[str, int]] = None
|
|
44
|
+
border_style: Optional[str] = None
|
|
45
|
+
border_radius: Optional[Union[str, int]] = None
|
|
46
|
+
|
|
47
|
+
# Layout
|
|
48
|
+
display: Optional[str] = None
|
|
49
|
+
position: Optional[str] = None
|
|
50
|
+
top: Optional[Union[str, int]] = None
|
|
51
|
+
right: Optional[Union[str, int]] = None
|
|
52
|
+
bottom: Optional[Union[str, int]] = None
|
|
53
|
+
left: Optional[Union[str, int]] = None
|
|
54
|
+
z_index: Optional[int] = None
|
|
55
|
+
|
|
56
|
+
# Flexbox
|
|
57
|
+
flex_direction: Optional[str] = None
|
|
58
|
+
flex_wrap: Optional[str] = None
|
|
59
|
+
justify_content: Optional[str] = None
|
|
60
|
+
align_items: Optional[str] = None
|
|
61
|
+
align_content: Optional[str] = None
|
|
62
|
+
flex: Optional[Union[str, int]] = None
|
|
63
|
+
flex_grow: Optional[int] = None
|
|
64
|
+
flex_shrink: Optional[int] = None
|
|
65
|
+
flex_basis: Optional[Union[str, int]] = None
|
|
66
|
+
|
|
67
|
+
# Grid
|
|
68
|
+
grid_template_columns: Optional[str] = None
|
|
69
|
+
grid_template_rows: Optional[str] = None
|
|
70
|
+
grid_gap: Optional[Union[str, int]] = None
|
|
71
|
+
grid_column: Optional[str] = None
|
|
72
|
+
grid_row: Optional[str] = None
|
|
73
|
+
|
|
74
|
+
# Efectos
|
|
75
|
+
opacity: Optional[float] = None
|
|
76
|
+
box_shadow: Optional[str] = None
|
|
77
|
+
transform: Optional[str] = None
|
|
78
|
+
transition: Optional[str] = None
|
|
79
|
+
|
|
80
|
+
# Overflow
|
|
81
|
+
overflow: Optional[str] = None
|
|
82
|
+
overflow_x: Optional[str] = None
|
|
83
|
+
overflow_y: Optional[str] = None
|
|
84
|
+
|
|
85
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
86
|
+
"""Convierte las propiedades a un diccionario, excluyendo valores None"""
|
|
87
|
+
result = {}
|
|
88
|
+
for key, value in self.__dict__.items():
|
|
89
|
+
if value is not None:
|
|
90
|
+
# Convertir snake_case a kebab-case para CSS
|
|
91
|
+
css_key = key.replace('_', '-')
|
|
92
|
+
result[css_key] = value
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class EventProps:
|
|
97
|
+
"""Properties of events for UI components"""
|
|
98
|
+
on_click: Optional[Callable] = None
|
|
99
|
+
on_double_click: Optional[Callable] = None
|
|
100
|
+
on_mouse_enter: Optional[Callable] = None
|
|
101
|
+
on_mouse_leave: Optional[Callable] = None
|
|
102
|
+
on_mouse_down: Optional[Callable] = None
|
|
103
|
+
on_mouse_up: Optional[Callable] = None
|
|
104
|
+
on_key_down: Optional[Callable] = None
|
|
105
|
+
on_key_up: Optional[Callable] = None
|
|
106
|
+
on_focus: Optional[Callable] = None
|
|
107
|
+
on_blur: Optional[Callable] = None
|
|
108
|
+
on_change: Optional[Callable] = None
|
|
109
|
+
on_input: Optional[Callable] = None
|
|
110
|
+
on_submit: Optional[Callable] = None
|
|
111
|
+
on_load: Optional[Callable] = None
|
|
112
|
+
on_error: Optional[Callable] = None
|
|
113
|
+
|
|
114
|
+
def normalize_style_value(value: Union[str, int]) -> str:
|
|
115
|
+
"""Normalizes a style value to a CSS string"""
|
|
116
|
+
if isinstance(value, int):
|
|
117
|
+
return f"{value}px"
|
|
118
|
+
return str(value)
|
|
119
|
+
|
|
120
|
+
def merge_styles(*styles: Dict[str, Any]) -> Dict[str, Any]:
|
|
121
|
+
"""Combines multiple style dictionaries"""
|
|
122
|
+
result = {}
|
|
123
|
+
for style in styles:
|
|
124
|
+
if style:
|
|
125
|
+
result.update(style)
|
|
126
|
+
return result
|
|
127
|
+
|