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.
Files changed (68) hide show
  1. dars/__init__.py +0 -0
  2. dars/all.py +52 -0
  3. dars/cli/__init__.py +0 -0
  4. dars/cli/hot_reload.py +33 -0
  5. dars/cli/main.py +637 -0
  6. dars/cli/preview.py +419 -0
  7. dars/cli/translations.py +389 -0
  8. dars/components/__init__.py +0 -0
  9. dars/components/advanced/__init__.py +8 -0
  10. dars/components/advanced/accordion.py +21 -0
  11. dars/components/advanced/card.py +28 -0
  12. dars/components/advanced/modal.py +40 -0
  13. dars/components/advanced/navbar.py +31 -0
  14. dars/components/advanced/table.py +24 -0
  15. dars/components/advanced/tabs.py +26 -0
  16. dars/components/basic/__init__.py +34 -0
  17. dars/components/basic/button.py +29 -0
  18. dars/components/basic/checkbox.py +34 -0
  19. dars/components/basic/container.py +23 -0
  20. dars/components/basic/datepicker.py +139 -0
  21. dars/components/basic/image.py +36 -0
  22. dars/components/basic/input.py +50 -0
  23. dars/components/basic/link.py +31 -0
  24. dars/components/basic/page.py +20 -0
  25. dars/components/basic/progressbar.py +17 -0
  26. dars/components/basic/radiobutton.py +34 -0
  27. dars/components/basic/select.py +81 -0
  28. dars/components/basic/slider.py +63 -0
  29. dars/components/basic/spinner.py +11 -0
  30. dars/components/basic/text.py +22 -0
  31. dars/components/basic/textarea.py +46 -0
  32. dars/components/basic/tooltip.py +18 -0
  33. dars/components/layout/__init__.py +0 -0
  34. dars/components/layout/anchor.py +13 -0
  35. dars/components/layout/flex.py +26 -0
  36. dars/components/layout/grid.py +45 -0
  37. dars/core/__init__.py +0 -0
  38. dars/core/app.py +630 -0
  39. dars/core/component.py +25 -0
  40. dars/core/events.py +101 -0
  41. dars/core/properties.py +127 -0
  42. dars/docs/__init__.py +0 -0
  43. dars/exporters/__init__.py +0 -0
  44. dars/exporters/base.py +69 -0
  45. dars/exporters/web/__init__.py +0 -0
  46. dars/exporters/web/html_css_js.py +1406 -0
  47. dars/scripts/__init__.py +0 -0
  48. dars/scripts/script.py +38 -0
  49. dars/templates/__init__.py +0 -0
  50. dars/templates/examples/advanced/all_components_demo.py +87 -0
  51. dars/templates/examples/advanced/dashboard.py +440 -0
  52. dars/templates/examples/advanced/modern_web_app.py +452 -0
  53. dars/templates/examples/basic/flex_layout_responsive.py +13 -0
  54. dars/templates/examples/basic/form_components.py +516 -0
  55. dars/templates/examples/basic/grid_layout_responsive.py +13 -0
  56. dars/templates/examples/basic/hello_world.py +104 -0
  57. dars/templates/examples/basic/layout_multipage_demo.py +23 -0
  58. dars/templates/examples/basic/multipage_example.py +70 -0
  59. dars/templates/examples/basic/pwa_custom_icons.py +31 -0
  60. dars/templates/examples/basic/simple_form.py +377 -0
  61. dars/templates/examples/demo/complete_app.py +720 -0
  62. dars/templates/html/__init__.py +0 -0
  63. dars_framework-1.0.0.dist-info/METADATA +146 -0
  64. dars_framework-1.0.0.dist-info/RECORD +68 -0
  65. dars_framework-1.0.0.dist-info/WHEEL +5 -0
  66. dars_framework-1.0.0.dist-info/entry_points.txt +2 -0
  67. dars_framework-1.0.0.dist-info/licenses/LICENSE +21 -0
  68. 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,11 @@
1
+ from dars.core.component import Component
2
+
3
+ class Spinner(Component):
4
+ """
5
+ Indicador de carga circular (spinner).
6
+ """
7
+ def __init__(self, **props):
8
+ super().__init__(**props)
9
+
10
+ def render(self) -> str:
11
+ return '<div class="dars-spinner"></div>'
@@ -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})"