dars-framework 1.0.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.
- dars/__init__.py +0 -0
- dars/all.py +52 -0
- dars/cli/__init__.py +0 -0
- dars/cli/hot_reload.py +33 -0
- dars/cli/main.py +637 -0
- dars/cli/preview.py +419 -0
- dars/cli/translations.py +389 -0
- dars/components/__init__.py +0 -0
- dars/components/advanced/__init__.py +8 -0
- dars/components/advanced/accordion.py +21 -0
- dars/components/advanced/card.py +28 -0
- dars/components/advanced/modal.py +40 -0
- dars/components/advanced/navbar.py +31 -0
- dars/components/advanced/table.py +24 -0
- dars/components/advanced/tabs.py +26 -0
- dars/components/basic/__init__.py +34 -0
- dars/components/basic/button.py +29 -0
- dars/components/basic/checkbox.py +34 -0
- dars/components/basic/container.py +23 -0
- dars/components/basic/datepicker.py +139 -0
- dars/components/basic/image.py +36 -0
- dars/components/basic/input.py +50 -0
- dars/components/basic/link.py +31 -0
- dars/components/basic/page.py +20 -0
- dars/components/basic/progressbar.py +17 -0
- dars/components/basic/radiobutton.py +34 -0
- dars/components/basic/select.py +81 -0
- dars/components/basic/slider.py +63 -0
- dars/components/basic/spinner.py +11 -0
- dars/components/basic/text.py +22 -0
- dars/components/basic/textarea.py +46 -0
- dars/components/basic/tooltip.py +18 -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/core/__init__.py +0 -0
- dars/core/app.py +630 -0
- dars/core/component.py +25 -0
- dars/core/events.py +101 -0
- dars/core/properties.py +127 -0
- dars/docs/__init__.py +0 -0
- dars/exporters/__init__.py +0 -0
- dars/exporters/base.py +69 -0
- dars/exporters/web/__init__.py +0 -0
- dars/exporters/web/html_css_js.py +1406 -0
- dars/scripts/__init__.py +0 -0
- dars/scripts/script.py +38 -0
- dars/templates/__init__.py +0 -0
- dars/templates/examples/advanced/all_components_demo.py +87 -0
- dars/templates/examples/advanced/dashboard.py +440 -0
- dars/templates/examples/advanced/modern_web_app.py +452 -0
- dars/templates/examples/basic/flex_layout_responsive.py +13 -0
- dars/templates/examples/basic/form_components.py +516 -0
- dars/templates/examples/basic/grid_layout_responsive.py +13 -0
- dars/templates/examples/basic/hello_world.py +104 -0
- dars/templates/examples/basic/layout_multipage_demo.py +23 -0
- dars/templates/examples/basic/multipage_example.py +70 -0
- dars/templates/examples/basic/pwa_custom_icons.py +31 -0
- dars/templates/examples/basic/simple_form.py +377 -0
- dars/templates/examples/demo/complete_app.py +720 -0
- dars/templates/html/__init__.py +0 -0
- dars_framework-1.0.0.dist-info/METADATA +146 -0
- dars_framework-1.0.0.dist-info/RECORD +68 -0
- dars_framework-1.0.0.dist-info/WHEEL +5 -0
- dars_framework-1.0.0.dist-info/entry_points.txt +2 -0
- dars_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
- dars_framework-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable
|
|
5
|
+
|
|
6
|
+
class Checkbox(Component):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
label: str = "",
|
|
10
|
+
checked: bool = False,
|
|
11
|
+
value: str = "",
|
|
12
|
+
name: Optional[str] = None,
|
|
13
|
+
id: Optional[str] = None,
|
|
14
|
+
class_name: Optional[str] = None,
|
|
15
|
+
style: Optional[Dict[str, Any]] = None,
|
|
16
|
+
disabled: bool = False,
|
|
17
|
+
required: bool = False,
|
|
18
|
+
on_change: Optional[Callable] = None
|
|
19
|
+
):
|
|
20
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
21
|
+
self.label = label
|
|
22
|
+
self.checked = checked
|
|
23
|
+
self.value = value or label # Si no se proporciona value, usar label
|
|
24
|
+
self.name = name
|
|
25
|
+
self.disabled = disabled
|
|
26
|
+
self.required = required
|
|
27
|
+
|
|
28
|
+
# Registrar evento de cambio si se proporciona
|
|
29
|
+
if on_change:
|
|
30
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
31
|
+
|
|
32
|
+
def render(self, exporter: Any) -> str:
|
|
33
|
+
# El método render será implementado por cada exportador
|
|
34
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from typing import Optional, Union, Dict, Any, List
|
|
4
|
+
|
|
5
|
+
class Container(Component):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
id: Optional[str] = None,
|
|
9
|
+
class_name: Optional[str] = None,
|
|
10
|
+
style: Optional[Dict[str, Any]] = None,
|
|
11
|
+
children: Optional[List[Component]] = None
|
|
12
|
+
):
|
|
13
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
14
|
+
|
|
15
|
+
# Agregar hijos si se proporcionan
|
|
16
|
+
if children:
|
|
17
|
+
for child in children:
|
|
18
|
+
self.add_child(child)
|
|
19
|
+
|
|
20
|
+
def render(self, exporter: Any) -> str:
|
|
21
|
+
# El método render será implementado por cada exportador
|
|
22
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
23
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable
|
|
5
|
+
from datetime import datetime, date
|
|
6
|
+
|
|
7
|
+
class DatePicker(Component):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
value: Optional[Union[str, date, datetime]] = None,
|
|
11
|
+
min_date: Optional[Union[str, date, datetime]] = None,
|
|
12
|
+
max_date: Optional[Union[str, date, datetime]] = None,
|
|
13
|
+
placeholder: str = "Seleccionar fecha",
|
|
14
|
+
format: str = "YYYY-MM-DD", # Formato de fecha
|
|
15
|
+
locale: str = "es", # Idioma para el picker
|
|
16
|
+
show_time: bool = False, # Si incluir selector de tiempo
|
|
17
|
+
inline: bool = False, # Si mostrar inline o como popup
|
|
18
|
+
disabled_dates: Optional[list] = None, # Fechas deshabilitadas
|
|
19
|
+
id: Optional[str] = None,
|
|
20
|
+
class_name: Optional[str] = None,
|
|
21
|
+
style: Optional[Dict[str, Any]] = None,
|
|
22
|
+
disabled: bool = False,
|
|
23
|
+
required: bool = False,
|
|
24
|
+
readonly: bool = False,
|
|
25
|
+
on_change: Optional[Callable] = None,
|
|
26
|
+
on_open: Optional[Callable] = None,
|
|
27
|
+
on_close: Optional[Callable] = None
|
|
28
|
+
):
|
|
29
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
30
|
+
|
|
31
|
+
# Asignar atributos básicos primero
|
|
32
|
+
self.placeholder = placeholder
|
|
33
|
+
self.format = format
|
|
34
|
+
self.locale = locale
|
|
35
|
+
self.show_time = show_time
|
|
36
|
+
self.inline = inline
|
|
37
|
+
self.disabled_dates = disabled_dates or []
|
|
38
|
+
self.disabled = disabled
|
|
39
|
+
self.required = required
|
|
40
|
+
self.readonly = readonly
|
|
41
|
+
|
|
42
|
+
# Validar formato
|
|
43
|
+
valid_formats = ["YYYY-MM-DD", "DD/MM/YYYY", "MM/DD/YYYY", "DD-MM-YYYY"]
|
|
44
|
+
if format not in valid_formats:
|
|
45
|
+
raise ValueError(f"format debe ser uno de: {valid_formats}")
|
|
46
|
+
|
|
47
|
+
# Validar locale
|
|
48
|
+
valid_locales = ["es", "en", "fr", "de", "it", "pt"]
|
|
49
|
+
if locale not in valid_locales:
|
|
50
|
+
raise ValueError(f"locale debe ser uno de: {valid_locales}")
|
|
51
|
+
|
|
52
|
+
# Ahora procesar las fechas (después de asignar self.format)
|
|
53
|
+
self.value = self._process_date(value)
|
|
54
|
+
self.min_date = self._process_date(min_date)
|
|
55
|
+
self.max_date = self._process_date(max_date)
|
|
56
|
+
|
|
57
|
+
# Registrar eventos si se proporcionan
|
|
58
|
+
if on_change:
|
|
59
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
60
|
+
if on_open:
|
|
61
|
+
self.set_event("open", on_open)
|
|
62
|
+
if on_close:
|
|
63
|
+
self.set_event("close", on_close)
|
|
64
|
+
|
|
65
|
+
def _process_date(self, date_value: Optional[Union[str, date, datetime]]) -> Optional[str]:
|
|
66
|
+
"""Procesa y normaliza el valor de fecha"""
|
|
67
|
+
if date_value is None:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
if isinstance(date_value, str):
|
|
71
|
+
# Validar formato de string de fecha
|
|
72
|
+
try:
|
|
73
|
+
# Intentar parsear la fecha para validarla
|
|
74
|
+
if self.format == "YYYY-MM-DD":
|
|
75
|
+
datetime.strptime(date_value, "%Y-%m-%d")
|
|
76
|
+
elif self.format == "DD/MM/YYYY":
|
|
77
|
+
datetime.strptime(date_value, "%d/%m/%Y")
|
|
78
|
+
elif self.format == "MM/DD/YYYY":
|
|
79
|
+
datetime.strptime(date_value, "%m/%d/%Y")
|
|
80
|
+
elif self.format == "DD-MM-YYYY":
|
|
81
|
+
datetime.strptime(date_value, "%d-%m-%Y")
|
|
82
|
+
return date_value
|
|
83
|
+
except ValueError:
|
|
84
|
+
raise ValueError(f"Formato de fecha inválido: {date_value}")
|
|
85
|
+
|
|
86
|
+
elif isinstance(date_value, (date, datetime)):
|
|
87
|
+
# Convertir objeto date/datetime a string según el formato
|
|
88
|
+
if self.format == "YYYY-MM-DD":
|
|
89
|
+
return date_value.strftime("%Y-%m-%d")
|
|
90
|
+
elif self.format == "DD/MM/YYYY":
|
|
91
|
+
return date_value.strftime("%d/%m/%Y")
|
|
92
|
+
elif self.format == "MM/DD/YYYY":
|
|
93
|
+
return date_value.strftime("%m/%d/%Y")
|
|
94
|
+
elif self.format == "DD-MM-YYYY":
|
|
95
|
+
return date_value.strftime("%d-%m-%Y")
|
|
96
|
+
|
|
97
|
+
return str(date_value)
|
|
98
|
+
|
|
99
|
+
def set_value(self, value: Union[str, date, datetime]):
|
|
100
|
+
"""Establece el valor de la fecha"""
|
|
101
|
+
self.value = self._process_date(value)
|
|
102
|
+
|
|
103
|
+
def get_date_object(self) -> Optional[datetime]:
|
|
104
|
+
"""Obtiene el valor como objeto datetime"""
|
|
105
|
+
if not self.value:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
if self.format == "YYYY-MM-DD":
|
|
110
|
+
return datetime.strptime(self.value, "%Y-%m-%d")
|
|
111
|
+
elif self.format == "DD/MM/YYYY":
|
|
112
|
+
return datetime.strptime(self.value, "%d/%m/%Y")
|
|
113
|
+
elif self.format == "MM/DD/YYYY":
|
|
114
|
+
return datetime.strptime(self.value, "%m/%d/%Y")
|
|
115
|
+
elif self.format == "DD-MM-YYYY":
|
|
116
|
+
return datetime.strptime(self.value, "%d-%m-%Y")
|
|
117
|
+
except ValueError:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def is_date_disabled(self, date_to_check: Union[str, date, datetime]) -> bool:
|
|
121
|
+
"""Verifica si una fecha está deshabilitada"""
|
|
122
|
+
check_date = self._process_date(date_to_check)
|
|
123
|
+
return check_date in self.disabled_dates
|
|
124
|
+
|
|
125
|
+
def add_disabled_date(self, date_to_disable: Union[str, date, datetime]):
|
|
126
|
+
"""Añade una fecha a la lista de fechas deshabilitadas"""
|
|
127
|
+
disabled_date = self._process_date(date_to_disable)
|
|
128
|
+
if disabled_date and disabled_date not in self.disabled_dates:
|
|
129
|
+
self.disabled_dates.append(disabled_date)
|
|
130
|
+
|
|
131
|
+
def remove_disabled_date(self, date_to_enable: Union[str, date, datetime]):
|
|
132
|
+
"""Elimina una fecha de la lista de fechas deshabilitadas"""
|
|
133
|
+
enabled_date = self._process_date(date_to_enable)
|
|
134
|
+
if enabled_date in self.disabled_dates:
|
|
135
|
+
self.disabled_dates.remove(enabled_date)
|
|
136
|
+
|
|
137
|
+
def render(self, exporter: Any) -> str:
|
|
138
|
+
# El método render será implementado por cada exportador
|
|
139
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
class Image(Component):
|
|
5
|
+
"""Componente para mostrar imágenes."""
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
src: str,
|
|
9
|
+
alt: str = "",
|
|
10
|
+
width: Optional[str] = None,
|
|
11
|
+
height: Optional[str] = None,
|
|
12
|
+
class_name: Optional[str] = None,
|
|
13
|
+
style: Optional[Dict[str, Any]] = None,
|
|
14
|
+
**kwargs
|
|
15
|
+
):
|
|
16
|
+
super().__init__(class_name=class_name, style=style, **kwargs)
|
|
17
|
+
self.src = src
|
|
18
|
+
self.alt = alt
|
|
19
|
+
self.width = width
|
|
20
|
+
self.height = height
|
|
21
|
+
|
|
22
|
+
def render(self) -> str:
|
|
23
|
+
attrs = [
|
|
24
|
+
f'src="{self.src}"',
|
|
25
|
+
f'alt="{self.alt}"',
|
|
26
|
+
]
|
|
27
|
+
if self.width: attrs.append(f'width="{self.width}"')
|
|
28
|
+
if self.height: attrs.append(f'height="{self.height}"')
|
|
29
|
+
if self.class_name: attrs.append(f'class="{self.class_name}"')
|
|
30
|
+
if self.style: attrs.append(f'style="{self.render_styles(self.style)}"')
|
|
31
|
+
|
|
32
|
+
return f'<img {" ".join(attrs)} />'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable
|
|
5
|
+
|
|
6
|
+
class Input(Component):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
value: str = "",
|
|
10
|
+
placeholder: str = "",
|
|
11
|
+
input_type: str = "text", # "text", "password", "email", "number", etc.
|
|
12
|
+
id: Optional[str] = None,
|
|
13
|
+
class_name: Optional[str] = None,
|
|
14
|
+
style: Optional[Dict[str, Any]] = None,
|
|
15
|
+
disabled: bool = False,
|
|
16
|
+
readonly: bool = False,
|
|
17
|
+
required: bool = False,
|
|
18
|
+
max_length: Optional[int] = None,
|
|
19
|
+
min_length: Optional[int] = None,
|
|
20
|
+
pattern: Optional[str] = None,
|
|
21
|
+
on_change: Optional[Callable] = None,
|
|
22
|
+
on_input: Optional[Callable] = None,
|
|
23
|
+
on_focus: Optional[Callable] = None,
|
|
24
|
+
on_blur: Optional[Callable] = None
|
|
25
|
+
):
|
|
26
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
27
|
+
self.value = value
|
|
28
|
+
self.placeholder = placeholder
|
|
29
|
+
self.input_type = input_type
|
|
30
|
+
self.disabled = disabled
|
|
31
|
+
self.readonly = readonly
|
|
32
|
+
self.required = required
|
|
33
|
+
self.max_length = max_length
|
|
34
|
+
self.min_length = min_length
|
|
35
|
+
self.pattern = pattern
|
|
36
|
+
|
|
37
|
+
# Registrar eventos si se proporcionan
|
|
38
|
+
if on_change:
|
|
39
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
40
|
+
if on_input:
|
|
41
|
+
self.set_event(EventTypes.INPUT, on_input)
|
|
42
|
+
if on_focus:
|
|
43
|
+
self.set_event(EventTypes.FOCUS, on_focus)
|
|
44
|
+
if on_blur:
|
|
45
|
+
self.set_event(EventTypes.BLUR, on_blur)
|
|
46
|
+
|
|
47
|
+
def render(self, exporter: Any) -> str:
|
|
48
|
+
# El método render será implementado por cada exportador
|
|
49
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
50
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
class Link(Component):
|
|
5
|
+
"""Componente para crear enlaces."""
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
text: str,
|
|
9
|
+
href: str,
|
|
10
|
+
target: str = "_self",
|
|
11
|
+
class_name: Optional[str] = None,
|
|
12
|
+
style: Optional[Dict[str, Any]] = None,
|
|
13
|
+
**kwargs
|
|
14
|
+
):
|
|
15
|
+
super().__init__(class_name=class_name, style=style, **kwargs)
|
|
16
|
+
self.text = text
|
|
17
|
+
self.href = href
|
|
18
|
+
self.target = target
|
|
19
|
+
|
|
20
|
+
def render(self) -> str:
|
|
21
|
+
attrs = [
|
|
22
|
+
f'href="{self.href}"',
|
|
23
|
+
f'target="{self.target}"',
|
|
24
|
+
]
|
|
25
|
+
if self.class_name: attrs.append(f'class="{self.class_name}"')
|
|
26
|
+
if self.style: attrs.append(f'style="{self.render_styles(self.style)}"')
|
|
27
|
+
|
|
28
|
+
return f'<a {" ".join(attrs)}>{self.text}</a>'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any, List
|
|
3
|
+
|
|
4
|
+
class Page(Component):
|
|
5
|
+
"""Componente raíz para páginas en apps multipágina Dars. Permite pasar hijos como argumentos posicionales y scripts por página."""
|
|
6
|
+
def __init__(self, *children: Component, id: Optional[str] = None, class_name: Optional[str] = None, style: Optional[Dict[str, Any]] = None):
|
|
7
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
8
|
+
self.scripts = []
|
|
9
|
+
for child in children:
|
|
10
|
+
self.add_child(child)
|
|
11
|
+
|
|
12
|
+
def add_script(self, script):
|
|
13
|
+
self.scripts.append(script)
|
|
14
|
+
|
|
15
|
+
def get_scripts(self):
|
|
16
|
+
return self.scripts
|
|
17
|
+
|
|
18
|
+
def render(self, exporter: Any) -> str:
|
|
19
|
+
# El método render será implementado por el exporter
|
|
20
|
+
raise NotImplementedError("El método render debe ser implementado por el exporter")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class ProgressBar(Component):
|
|
5
|
+
"""
|
|
6
|
+
Barra de progreso visual.
|
|
7
|
+
value: valor actual (0-100)
|
|
8
|
+
max_value: valor máximo (default 100)
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, value: int, max_value: int = 100, **props):
|
|
11
|
+
super().__init__(**props)
|
|
12
|
+
self.value = value
|
|
13
|
+
self.max_value = max_value
|
|
14
|
+
|
|
15
|
+
def render(self) -> str:
|
|
16
|
+
percent = min(max(self.value / self.max_value * 100, 0), 100)
|
|
17
|
+
return f'<div class="dars-progressbar"><div class="dars-progressbar-bar" style="width: {percent}%;"></div></div>'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable
|
|
5
|
+
|
|
6
|
+
class RadioButton(Component):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
label: str = "",
|
|
10
|
+
value: str = "",
|
|
11
|
+
name: str = "radio_group",
|
|
12
|
+
checked: bool = False,
|
|
13
|
+
id: Optional[str] = None,
|
|
14
|
+
class_name: Optional[str] = None,
|
|
15
|
+
style: Optional[Dict[str, Any]] = None,
|
|
16
|
+
disabled: bool = False,
|
|
17
|
+
required: bool = False,
|
|
18
|
+
on_change: Optional[Callable] = None
|
|
19
|
+
):
|
|
20
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
21
|
+
self.label = label
|
|
22
|
+
self.value = value or label # Si no se proporciona value, usar label
|
|
23
|
+
self.name = name # Requerido para agrupar radio buttons
|
|
24
|
+
self.checked = checked
|
|
25
|
+
self.disabled = disabled
|
|
26
|
+
self.required = required
|
|
27
|
+
|
|
28
|
+
# Registrar evento de cambio si se proporciona
|
|
29
|
+
if on_change:
|
|
30
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
31
|
+
|
|
32
|
+
def render(self, exporter: Any) -> str:
|
|
33
|
+
# El método render será implementado por cada exportador
|
|
34
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable, List
|
|
5
|
+
|
|
6
|
+
class SelectOption:
|
|
7
|
+
"""Clase para representar una opción del select"""
|
|
8
|
+
def __init__(self, value: str, label: str, disabled: bool = False):
|
|
9
|
+
self.value = value
|
|
10
|
+
self.label = label
|
|
11
|
+
self.disabled = disabled
|
|
12
|
+
|
|
13
|
+
class Select(Component):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
options: List[Union[SelectOption, Dict[str, Any], str]] = None,
|
|
17
|
+
value: Optional[str] = None,
|
|
18
|
+
placeholder: str = "Seleccionar...",
|
|
19
|
+
multiple: bool = False,
|
|
20
|
+
size: Optional[int] = None,
|
|
21
|
+
id: Optional[str] = None,
|
|
22
|
+
class_name: Optional[str] = None,
|
|
23
|
+
style: Optional[Dict[str, Any]] = None,
|
|
24
|
+
disabled: bool = False,
|
|
25
|
+
required: bool = False,
|
|
26
|
+
on_change: Optional[Callable] = None
|
|
27
|
+
):
|
|
28
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
29
|
+
self.options = self._process_options(options or [])
|
|
30
|
+
self.value = value
|
|
31
|
+
self.placeholder = placeholder
|
|
32
|
+
self.multiple = multiple
|
|
33
|
+
self.size = size # Número de opciones visibles (para select múltiple)
|
|
34
|
+
self.disabled = disabled
|
|
35
|
+
self.required = required
|
|
36
|
+
|
|
37
|
+
# Registrar evento de cambio si se proporciona
|
|
38
|
+
if on_change:
|
|
39
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
40
|
+
|
|
41
|
+
def _process_options(self, options: List[Union[SelectOption, Dict[str, Any], str]]) -> List[SelectOption]:
|
|
42
|
+
"""Procesa las opciones y las convierte a objetos SelectOption"""
|
|
43
|
+
processed_options = []
|
|
44
|
+
|
|
45
|
+
for option in options:
|
|
46
|
+
if isinstance(option, SelectOption):
|
|
47
|
+
processed_options.append(option)
|
|
48
|
+
elif isinstance(option, dict):
|
|
49
|
+
processed_options.append(SelectOption(
|
|
50
|
+
value=option.get('value', ''),
|
|
51
|
+
label=option.get('label', option.get('value', '')),
|
|
52
|
+
disabled=option.get('disabled', False)
|
|
53
|
+
))
|
|
54
|
+
elif isinstance(option, str):
|
|
55
|
+
processed_options.append(SelectOption(value=option, label=option))
|
|
56
|
+
|
|
57
|
+
return processed_options
|
|
58
|
+
|
|
59
|
+
def add_option(self, value: str, label: str = None, disabled: bool = False):
|
|
60
|
+
"""Añade una nueva opción al select"""
|
|
61
|
+
self.options.append(SelectOption(
|
|
62
|
+
value=value,
|
|
63
|
+
label=label or value,
|
|
64
|
+
disabled=disabled
|
|
65
|
+
))
|
|
66
|
+
|
|
67
|
+
def remove_option(self, value: str):
|
|
68
|
+
"""Elimina una opción por su valor"""
|
|
69
|
+
self.options = [opt for opt in self.options if opt.value != value]
|
|
70
|
+
|
|
71
|
+
def get_selected_option(self) -> Optional[SelectOption]:
|
|
72
|
+
"""Obtiene la opción seleccionada actualmente"""
|
|
73
|
+
if self.value:
|
|
74
|
+
for option in self.options:
|
|
75
|
+
if option.value == self.value:
|
|
76
|
+
return option
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
def render(self, exporter: Any) -> str:
|
|
80
|
+
# El método render será implementado por cada exportador
|
|
81
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable
|
|
5
|
+
|
|
6
|
+
class Slider(Component):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
min_value: Union[int, float] = 0,
|
|
10
|
+
max_value: Union[int, float] = 100,
|
|
11
|
+
value: Union[int, float] = 50,
|
|
12
|
+
step: Union[int, float] = 1,
|
|
13
|
+
label: str = "",
|
|
14
|
+
show_value: bool = True,
|
|
15
|
+
orientation: str = "horizontal", # "horizontal" o "vertical"
|
|
16
|
+
id: Optional[str] = None,
|
|
17
|
+
class_name: Optional[str] = None,
|
|
18
|
+
style: Optional[Dict[str, Any]] = None,
|
|
19
|
+
disabled: bool = False,
|
|
20
|
+
on_change: Optional[Callable] = None,
|
|
21
|
+
on_input: Optional[Callable] = None
|
|
22
|
+
):
|
|
23
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
24
|
+
self.min_value = min_value
|
|
25
|
+
self.max_value = max_value
|
|
26
|
+
self.value = max(min_value, min(max_value, value)) # Asegurar que esté en rango
|
|
27
|
+
self.step = step
|
|
28
|
+
self.label = label
|
|
29
|
+
self.show_value = show_value
|
|
30
|
+
self.orientation = orientation
|
|
31
|
+
self.disabled = disabled
|
|
32
|
+
|
|
33
|
+
# Validar orientación
|
|
34
|
+
if orientation not in ["horizontal", "vertical"]:
|
|
35
|
+
raise ValueError("orientation debe ser 'horizontal' o 'vertical'")
|
|
36
|
+
|
|
37
|
+
# Registrar eventos si se proporcionan
|
|
38
|
+
if on_change:
|
|
39
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
40
|
+
if on_input:
|
|
41
|
+
self.set_event(EventTypes.INPUT, on_input)
|
|
42
|
+
|
|
43
|
+
def set_value(self, value: Union[int, float]):
|
|
44
|
+
"""Establece el valor del slider asegurando que esté en rango"""
|
|
45
|
+
self.value = max(self.min_value, min(self.max_value, value))
|
|
46
|
+
|
|
47
|
+
def get_percentage(self) -> float:
|
|
48
|
+
"""Obtiene el porcentaje actual del slider (0-100)"""
|
|
49
|
+
if self.max_value == self.min_value:
|
|
50
|
+
return 0
|
|
51
|
+
return ((self.value - self.min_value) / (self.max_value - self.min_value)) * 100
|
|
52
|
+
|
|
53
|
+
def is_at_min(self) -> bool:
|
|
54
|
+
"""Verifica si el slider está en su valor mínimo"""
|
|
55
|
+
return self.value == self.min_value
|
|
56
|
+
|
|
57
|
+
def is_at_max(self) -> bool:
|
|
58
|
+
"""Verifica si el slider está en su valor máximo"""
|
|
59
|
+
return self.value == self.max_value
|
|
60
|
+
|
|
61
|
+
def render(self, exporter: Any) -> str:
|
|
62
|
+
# El método render será implementado por cada exportador
|
|
63
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from typing import Optional, Union, Dict, Any
|
|
4
|
+
|
|
5
|
+
class Text(Component):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
text: str = "",
|
|
9
|
+
id: Optional[str] = None,
|
|
10
|
+
class_name: Optional[str] = None,
|
|
11
|
+
style: Optional[Dict[str, Any]] = None
|
|
12
|
+
):
|
|
13
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
14
|
+
self.text = text
|
|
15
|
+
|
|
16
|
+
def render(self, exporter: Any) -> str:
|
|
17
|
+
# El método render será implementado por cada exportador
|
|
18
|
+
# para generar el código específico de la plataforma.
|
|
19
|
+
# Por ahora, solo definimos la interfaz.
|
|
20
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
class Textarea(Component):
|
|
5
|
+
"""Componente para áreas de texto multilínea."""
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
value: str = "",
|
|
9
|
+
placeholder: str = "",
|
|
10
|
+
rows: int = 4,
|
|
11
|
+
cols: int = 50,
|
|
12
|
+
disabled: bool = False,
|
|
13
|
+
readonly: bool = False,
|
|
14
|
+
required: bool = False,
|
|
15
|
+
max_length: Optional[int] = None,
|
|
16
|
+
class_name: Optional[str] = None,
|
|
17
|
+
style: Optional[Dict[str, Any]] = None,
|
|
18
|
+
**kwargs
|
|
19
|
+
):
|
|
20
|
+
super().__init__(class_name=class_name, style=style, **kwargs)
|
|
21
|
+
self.value = value
|
|
22
|
+
self.placeholder = placeholder
|
|
23
|
+
self.rows = rows
|
|
24
|
+
self.cols = cols
|
|
25
|
+
self.disabled = disabled
|
|
26
|
+
self.readonly = readonly
|
|
27
|
+
self.required = required
|
|
28
|
+
self.max_length = max_length
|
|
29
|
+
|
|
30
|
+
def render(self) -> str:
|
|
31
|
+
attrs = [
|
|
32
|
+
f'rows="{self.rows}"',
|
|
33
|
+
f'cols="{self.cols}"',
|
|
34
|
+
]
|
|
35
|
+
if self.placeholder: attrs.append(f'placeholder="{self.placeholder}"')
|
|
36
|
+
if self.disabled: attrs.append('disabled')
|
|
37
|
+
if self.readonly: attrs.append('readonly')
|
|
38
|
+
if self.required: attrs.append('required')
|
|
39
|
+
if self.max_length: attrs.append(f'maxlength="{self.max_length}"')
|
|
40
|
+
if self.class_name: attrs.append(f'class="{self.class_name}"')
|
|
41
|
+
if self.style: attrs.append(f'style="{self.render_styles(self.style)}"')
|
|
42
|
+
|
|
43
|
+
return f'<textarea {" ".join(attrs)}>{self.value}</textarea>'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class Tooltip(Component):
|
|
5
|
+
"""
|
|
6
|
+
Tooltip: cuadro de información al pasar el cursor.
|
|
7
|
+
text: texto a mostrar
|
|
8
|
+
child: componente o HTML envuelto
|
|
9
|
+
position: top, right, bottom, left (opcional)
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, text: str, child: Component, position: Optional[str] = "top", **props):
|
|
12
|
+
super().__init__(**props)
|
|
13
|
+
self.text = text
|
|
14
|
+
self.child = child
|
|
15
|
+
self.position = position
|
|
16
|
+
|
|
17
|
+
def render(self) -> str:
|
|
18
|
+
return f'<div class="dars-tooltip dars-tooltip-{self.position}">{self.child.render() if hasattr(self.child, "render") else self.child}<span class="dars-tooltip-text">{self.text}</span></div>'
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
class AnchorPoint:
|
|
4
|
+
"""
|
|
5
|
+
Represents an anchor or alignment point for a child in a layout (top, left, right, bottom, center, etc).
|
|
6
|
+
"""
|
|
7
|
+
def __init__(self, x: Optional[str] = None, y: Optional[str] = None, name: Optional[str] = None):
|
|
8
|
+
self.x = x # e.g. 'left', 'center', 'right', percent or px
|
|
9
|
+
self.y = y # e.g. 'top', 'center', 'bottom', percent or px
|
|
10
|
+
self.name = name # Optional semantic name for anchor
|
|
11
|
+
|
|
12
|
+
def __repr__(self):
|
|
13
|
+
return f"AnchorPoint(x={self.x}, y={self.y}, name={self.name})"
|