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.
Files changed (118) hide show
  1. dars/__init__.py +0 -0
  2. dars/all.py +69 -0
  3. dars/cli/__init__.py +0 -0
  4. dars/cli/doctor/__init__.py +1 -0
  5. dars/cli/doctor/detect.py +154 -0
  6. dars/cli/doctor/doctor.py +176 -0
  7. dars/cli/doctor/installers.py +100 -0
  8. dars/cli/doctor/persist.py +62 -0
  9. dars/cli/doctor/preflight.py +33 -0
  10. dars/cli/doctor/ui.py +54 -0
  11. dars/cli/hot_reload.py +33 -0
  12. dars/cli/main.py +1107 -0
  13. dars/cli/preview.py +448 -0
  14. dars/cli/translations.py +531 -0
  15. dars/components/__init__.py +0 -0
  16. dars/components/advanced/__init__.py +8 -0
  17. dars/components/advanced/accordion.py +26 -0
  18. dars/components/advanced/card.py +33 -0
  19. dars/components/advanced/modal.py +45 -0
  20. dars/components/advanced/navbar.py +44 -0
  21. dars/components/advanced/table.py +25 -0
  22. dars/components/advanced/tabs.py +31 -0
  23. dars/components/basic/__init__.py +34 -0
  24. dars/components/basic/button.py +55 -0
  25. dars/components/basic/checkbox.py +35 -0
  26. dars/components/basic/container.py +29 -0
  27. dars/components/basic/datepicker.py +139 -0
  28. dars/components/basic/image.py +36 -0
  29. dars/components/basic/input.py +57 -0
  30. dars/components/basic/link.py +31 -0
  31. dars/components/basic/markdown.py +86 -0
  32. dars/components/basic/page.py +20 -0
  33. dars/components/basic/progressbar.py +18 -0
  34. dars/components/basic/radiobutton.py +35 -0
  35. dars/components/basic/select.py +82 -0
  36. dars/components/basic/slider.py +63 -0
  37. dars/components/basic/spinner.py +12 -0
  38. dars/components/basic/text.py +23 -0
  39. dars/components/basic/textarea.py +46 -0
  40. dars/components/basic/tooltip.py +19 -0
  41. dars/components/layout/__init__.py +0 -0
  42. dars/components/layout/anchor.py +13 -0
  43. dars/components/layout/flex.py +26 -0
  44. dars/components/layout/grid.py +45 -0
  45. dars/config.py +134 -0
  46. dars/core/__init__.py +0 -0
  47. dars/core/app.py +957 -0
  48. dars/core/component.py +284 -0
  49. dars/core/events.py +102 -0
  50. dars/core/js_bridge.py +99 -0
  51. dars/core/properties.py +127 -0
  52. dars/core/state.py +309 -0
  53. dars/dars_tests/apps_test/health_check.py +56 -0
  54. dars/dars_tests/run_tests.py +275 -0
  55. dars/dars_tests/tests/test_advanced_components.py +69 -0
  56. dars/dars_tests/tests/test_basic_components.py +88 -0
  57. dars/dars_tests/tests/test_core_and_cli.py +17 -0
  58. dars/dars_tests/tests/test_layout_components.py +58 -0
  59. dars/dars_tests/tests/test_version_check.py +21 -0
  60. dars/docs/__init__.py +0 -0
  61. dars/docs/app.md +290 -0
  62. dars/docs/cli.md +80 -0
  63. dars/docs/components.md +1679 -0
  64. dars/docs/custom_components.md +30 -0
  65. dars/docs/events.md +45 -0
  66. dars/docs/exporters.md +162 -0
  67. dars/docs/getting_started.md +79 -0
  68. dars/docs/index.md +18 -0
  69. dars/docs/scripts.md +593 -0
  70. dars/docs/state_management.md +57 -0
  71. dars/exporters/__init__.py +0 -0
  72. dars/exporters/base.py +96 -0
  73. dars/exporters/web/OLD/html_css_js_OLD4.py +1538 -0
  74. dars/exporters/web/OLD/html_css_js_old.py +1406 -0
  75. dars/exporters/web/OLD/html_css_js_old2.py +1406 -0
  76. dars/exporters/web/__init__.py +0 -0
  77. dars/exporters/web/html_css_js.py +2675 -0
  78. dars/exporters/web/vdom.py +251 -0
  79. dars/js_lib.py +206 -0
  80. dars/scripts/__init__.py +0 -0
  81. dars/scripts/dscript.py +26 -0
  82. dars/scripts/script.py +39 -0
  83. dars/security.py +195 -0
  84. dars/templates/__init__.py +0 -0
  85. dars/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  86. dars/templates/examples/README.md +4 -0
  87. dars/templates/examples/__pycache__/dynamic_event_demo.cpython-311.pyc +0 -0
  88. dars/templates/examples/advanced/Modal_Demo/advanced_modal_demo.py +275 -0
  89. dars/templates/examples/advanced/SimpleDashboard/dashboard.py +437 -0
  90. dars/templates/examples/advanced/SimpleModermWeb/modern_web_app.py +452 -0
  91. dars/templates/examples/advanced/VariousComponents/all_components_demo.py +87 -0
  92. dars/templates/examples/advanced/__init__.py +0 -0
  93. dars/templates/examples/advanced/dState/state_mods_demo.py +68 -0
  94. dars/templates/examples/basic/Forms/form_components.py +516 -0
  95. dars/templates/examples/basic/Forms/simple_form.py +379 -0
  96. dars/templates/examples/basic/HelloWorld/hello_world.py +56 -0
  97. dars/templates/examples/basic/Layouts/flex_layout_responsive.py +13 -0
  98. dars/templates/examples/basic/Layouts/grid_layout_responsive.py +12 -0
  99. dars/templates/examples/basic/Layouts/layout_multipage_demo.py +23 -0
  100. dars/templates/examples/basic/Multipage/multipage_example.py +67 -0
  101. dars/templates/examples/basic/PWA/icon-192x192.png +0 -0
  102. dars/templates/examples/basic/PWA/icon-512x512.png +0 -0
  103. dars/templates/examples/basic/PWA/pwa_custom_icons.py +33 -0
  104. dars/templates/examples/basic/__init__.py +0 -0
  105. dars/templates/examples/demo/__pycache__/complete_app.cpython-311.pyc +0 -0
  106. dars/templates/examples/demo/complete_app.py +21 -0
  107. dars/templates/examples/markdown/MarkdownTemplate/README.md +159 -0
  108. dars/templates/examples/markdown/MarkdownTemplate/markdown_template.py +21 -0
  109. dars/templates/examples/markdown/MarkdownTemplate/other_docs.md +1 -0
  110. dars/templates/examples/markdown/__init__.py +0 -0
  111. dars/templates/html/__init__.py +0 -0
  112. dars/version.py +2 -0
  113. dars_framework-1.2.3.dist-info/METADATA +15 -0
  114. dars_framework-1.2.3.dist-info/RECORD +118 -0
  115. dars_framework-1.2.3.dist-info/WHEEL +5 -0
  116. dars_framework-1.2.3.dist-info/entry_points.txt +2 -0
  117. dars_framework-1.2.3.dist-info/licenses/LICENSE +21 -0
  118. dars_framework-1.2.3.dist-info/top_level.txt +1 -0
dars/docs/scripts.md ADDED
@@ -0,0 +1,593 @@
1
+ # Dars - Script System
2
+
3
+ ## Introduction
4
+
5
+ The script system of Dars allows adding interactive logic and dynamic behaviors to applications. Scripts are written in JavaScript and seamlessly integrate with UI components.
6
+
7
+ ## Fundamentals
8
+
9
+ ### What are Scripts?
10
+
11
+ Scripts in Dars are fragments of JavaScript code that:
12
+
13
+ - Handle user interface events
14
+ - Implement client-side business logic
15
+ - Provide advanced interactivity
16
+ - Run in the context of the exported application
17
+
18
+ ### Types of Scripts
19
+
20
+ Dars supports three main types of scripts:
21
+
22
+ 1. **InlineScript**: Code defined directly in Python
23
+ 2. **FileScript**: Code loaded from external files
24
+ 3. **dScript**: Flexible script that can be defined either inline (as a string) or as a reference to an external file. Only one mode is allowed at a time.
25
+
26
+ ## Base Script Class
27
+
28
+ All scripts inherit from the base `Script` class:
29
+
30
+ ```python
31
+ from abc import ABC, abstractmethod
32
+
33
+ class Script(ABC):
34
+ def __init__(self):
35
+ pass
36
+
37
+ @abstractmethod
38
+ def get_code(self) -> str:
39
+ """Retorna el código del script"""
40
+ pass
41
+ ```
42
+
43
+ ## dScript
44
+
45
+ ### When to use dScript
46
+
47
+ dScript is a flexible class that allows you to define a script as either:
48
+ - Inline JavaScript (via the `code` argument)
49
+ - Or as a reference to an external file (via the `file_path` argument)
50
+
51
+ But **never both at the same time**. This is useful for presets, user-editable actions, and advanced integrations.
52
+
53
+ ### Basic Syntax
54
+
55
+ ```python
56
+ from dars.scripts.dscript import dScript
57
+
58
+ # Inline JS
59
+ script_inline = dScript(code="""
60
+ function hello() { alert('Hello from dScript!'); }
61
+ document.addEventListener('DOMContentLoaded', hello);
62
+ """)
63
+
64
+ # External file
65
+ script_file = dScript(file_path="./scripts/my_script.js")
66
+ ```
67
+
68
+ ### Example: Editable JS preset from Python
69
+
70
+ ```python
71
+ from dars.scripts.dscript import dScript
72
+
73
+ custom_action = dScript(code="""
74
+ function customClick() {
75
+ alert('Custom action from preset!');
76
+ }
77
+ document.addEventListener('DOMContentLoaded', function() {
78
+ var btn = document.getElementById('my-btn');
79
+ if (btn) btn.onclick = customClick;
80
+ });
81
+ """)
82
+
83
+ app.add_script(custom_action)
84
+ ```
85
+
86
+ ## InlineScript
87
+
88
+ ### Basic Syntax
89
+
90
+ ```python
91
+ from dars.scripts.script import InlineScript
92
+
93
+ script = InlineScript("""
94
+ function saludar() {
95
+ alert('¡Hola desde Dars!');
96
+ }
97
+
98
+ document.addEventListener('DOMContentLoaded', function() {
99
+ console.log('Aplicación cargada');
100
+ });
101
+ """)
102
+ ```
103
+
104
+ ### Practical Examples
105
+
106
+ #### Button Event Handling
107
+
108
+ ```python
109
+ script_botones = InlineScript("""
110
+ // Function to handle button clicks
111
+ function manejarClickBoton(evento) {
112
+ const boton = evento.target;
113
+ const texto = boton.textContent;
114
+
115
+ console.log(`Button pressed: ${texto}`);
116
+
117
+ // Change text temporarily
118
+ const textoOriginal = boton.textContent;
119
+ boton.textContent = '¡Presionado!';
120
+ boton.disabled = true;
121
+
122
+ setTimeout(() => {
123
+ boton.textContent = textoOriginal;
124
+ boton.disabled = false;
125
+ }, 1000);
126
+ }
127
+
128
+ // Add events to all buttons
129
+ document.addEventListener('DOMContentLoaded', function() {
130
+ const botones = document.querySelectorAll('button');
131
+ botones.forEach(boton => {
132
+ boton.addEventListener('click', manejarClickBoton);
133
+ });
134
+ });
135
+ """)
136
+ ```
137
+
138
+ #### Form Validation
139
+
140
+ ```python
141
+ script_validacion = InlineScript("""
142
+ // Form validation
143
+ function validarFormulario() {
144
+ const inputs = document.querySelectorAll('input[required]');
145
+ let esValido = true;
146
+
147
+ inputs.forEach(input => {
148
+ if (!input.value.trim()) {
149
+ mostrarError(input, 'This field is required');
150
+ esValido = false;
151
+ } else {
152
+ limpiarError(input);
153
+ }
154
+
155
+ // Specific type validation
156
+ if (input.type === 'email' && input.value) {
157
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
158
+ if (!emailRegex.test(input.value)) {
159
+ mostrarError(input, 'Email is invalid');
160
+ esValido = false;
161
+ }
162
+ }
163
+ });
164
+
165
+ return esValido;
166
+ }
167
+
168
+ function mostrarError(input, mensaje) {
169
+ // Remove previous error
170
+ limpiarError(input);
171
+
172
+ // Create error element
173
+ const error = document.createElement('div');
174
+ error.className = 'error-mensaje';
175
+ error.textContent = mensaje;
176
+ error.style.color = '#dc3545';
177
+ error.style.fontSize = '12px';
178
+ error.style.marginTop = '5px';
179
+
180
+ // Add after the input
181
+ input.parentNode.insertBefore(error, input.nextSibling);
182
+
183
+ // Change input style
184
+ input.style.borderColor = '#dc3545';
185
+ }
186
+
187
+ function limpiarError(input) {
188
+ const error = input.parentNode.querySelector('.error-mensaje');
189
+ if (error) {
190
+ error.remove();
191
+ }
192
+ input.style.borderColor = '';
193
+ }
194
+
195
+ // Configure real-time validation
196
+ document.addEventListener('DOMContentLoaded', function() {
197
+ const inputs = document.querySelectorAll('input');
198
+ inputs.forEach(input => {
199
+ input.addEventListener('blur', function() {
200
+ if (this.hasAttribute('required') && !this.value.trim()) {
201
+ mostrarError(this, 'This field is required');
202
+ } else {
203
+ limpiarError(this);
204
+ }
205
+ });
206
+
207
+ input.addEventListener('input', function() {
208
+ limpiarError(this);
209
+ });
210
+ });
211
+ });
212
+ """)
213
+ ```
214
+
215
+ #### Visual Effects and Animations
216
+
217
+ ```python
218
+ script_animaciones = InlineScript("""
219
+ // Fade in effect for elements
220
+ function fadeIn(elemento, duracion = 500) {
221
+ elemento.style.opacity = '0';
222
+ elemento.style.display = 'block';
223
+
224
+ const inicio = performance.now();
225
+
226
+ function animar(tiempo) {
227
+ const progreso = (tiempo - inicio) / duracion;
228
+
229
+ if (progreso < 1) {
230
+ elemento.style.opacity = progreso;
231
+ requestAnimationFrame(animar);
232
+ } else {
233
+ elemento.style.opacity = '1';
234
+ }
235
+ }
236
+
237
+ requestAnimationFrame(animar);
238
+ }
239
+
240
+ // Typing effect for text
241
+ function efectoTyping(elemento, texto, velocidad = 50) {
242
+ elemento.textContent = '';
243
+ let i = 0;
244
+
245
+ function escribir() {
246
+ if (i < texto.length) {
247
+ elemento.textContent += texto.charAt(i);
248
+ i++;
249
+ setTimeout(escribir, velocidad);
250
+ }
251
+ }
252
+
253
+ escribir();
254
+ }
255
+
256
+ // Parallax simple
257
+ function iniciarParallax() {
258
+ window.addEventListener('scroll', function() {
259
+ const scrolled = window.pageYOffset;
260
+ const elementos = document.querySelectorAll('.parallax');
261
+
262
+ elementos.forEach(elemento => {
263
+ const velocidad = elemento.dataset.velocidad || 0.5;
264
+ const yPos = -(scrolled * velocidad);
265
+ elemento.style.transform = `translateY(${yPos}px)`;
266
+ });
267
+ });
268
+ }
269
+
270
+ // Inicializar efectos
271
+ document.addEventListener('DOMContentLoaded', function() {
272
+ // Fade in para todos los elementos con clase 'fade-in'
273
+ const elementosFadeIn = document.querySelectorAll('.fade-in');
274
+ elementosFadeIn.forEach((elemento, index) => {
275
+ setTimeout(() => fadeIn(elemento), index * 200);
276
+ });
277
+
278
+ // Efecto typing para títulos
279
+ const titulos = document.querySelectorAll('.typing-effect');
280
+ titulos.forEach(titulo => {
281
+ const texto = titulo.textContent;
282
+ efectoTyping(titulo, texto);
283
+ });
284
+
285
+ // Inicializar parallax
286
+ iniciarParallax();
287
+ });
288
+ """)
289
+ ```
290
+
291
+ ### Integration with Exporter
292
+
293
+ The exporter (`html_css_js.py`) automatically detects and exports all scripts of type `dScript`, `InlineScript`, and `FileScript`. You can safely mix and match them in your app, and all will be included in the generated JS.
294
+
295
+ ---
296
+
297
+ ## FileScript
298
+
299
+ ### Basic Syntax
300
+
301
+ ```python
302
+ from dars.scripts.script import FileScript
303
+
304
+ # Load script from file
305
+ script = FileScript("./scripts/mi_script.js")
306
+ ```
307
+
308
+ ### File Organization
309
+
310
+ ```
311
+ mi_proyecto/
312
+ ├── app.py
313
+ ├── scripts/
314
+ │ ├── utils.js
315
+ │ ├── validaciones.js
316
+ │ └── animaciones.js
317
+ └────── api.js
318
+
319
+ ```
320
+
321
+ #### Example: utils.js
322
+
323
+ ```javascript
324
+ // scripts/utils.js
325
+
326
+ // General utilities
327
+ const Utils = {
328
+ // Date formatting
329
+ formatearFecha: function(fecha) {
330
+ return new Intl.DateTimeFormat('es-ES', {
331
+ year: 'numeric',
332
+ month: 'long',
333
+ day: 'numeric'
334
+ }).format(fecha);
335
+ },
336
+
337
+ // Debounce for event optimization
338
+ debounce: function(func, wait) {
339
+ let timeout;
340
+ return function executedFunction(...args) {
341
+ const later = () => {
342
+ clearTimeout(timeout);
343
+ func(...args);
344
+ };
345
+ clearTimeout(timeout);
346
+ timeout = setTimeout(later, wait);
347
+ };
348
+ },
349
+
350
+ // Email validation
351
+ esEmailValido: function(email) {
352
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
353
+ return regex.test(email);
354
+ },
355
+
356
+ // Generate unique ID
357
+ generarId: function() {
358
+ return '_'+ Math.random().toString(36).substr(2, 9);
359
+ },
360
+
361
+ // Local storage
362
+ guardarEnLocal: function(clave, valor) {
363
+ try {
364
+ localStorage.setItem(clave, JSON.stringify(valor));
365
+ return true;
366
+ } catch (e) {
367
+ console.error('Error al guardar en localStorage:', e);
368
+ return false;
369
+ }
370
+ },
371
+
372
+ obtenerDeLocal: function(clave) {
373
+ try {
374
+ const item = localStorage.getItem(clave);
375
+ return item ? JSON.parse(item) : null;
376
+ } catch (e) {
377
+ console.error('Error al leer de localStorage:', e);
378
+ return null;
379
+ }
380
+ }
381
+ };
382
+
383
+ // Make available globally
384
+ window.Utils = Utils;
385
+ ```
386
+
387
+ #### Example: api.js
388
+
389
+ ```javascript
390
+ // scripts/api.js
391
+
392
+ // API client
393
+ class ApiClient {
394
+ constructor(baseUrl) {
395
+ this.baseUrl = baseUrl;
396
+ this.headers = {
397
+ 'Content-Type': 'application/json'
398
+ };
399
+ }
400
+
401
+ async request(endpoint, options = {}) {
402
+ const url = `${this.baseUrl}${endpoint}`;
403
+ const config = {
404
+ headers: this.headers,
405
+ ...options
406
+ };
407
+
408
+ try {
409
+ const response = await fetch(url, config);
410
+
411
+ if (!response.ok) {
412
+ throw new Error(`HTTP error! status: ${response.status}`);
413
+ }
414
+
415
+ return await response.json();
416
+ } catch (error) {
417
+ console.error('Error en la petición:', error);
418
+ throw error;
419
+ }
420
+ }
421
+
422
+ async get(endpoint) {
423
+ return this.request(endpoint, { method: 'GET' });
424
+ }
425
+
426
+ async post(endpoint, data) {
427
+ return this.request(endpoint, {
428
+ method: 'POST',
429
+ body: JSON.stringify(data)
430
+ });
431
+ }
432
+
433
+ async put(endpoint, data) {
434
+ return this.request(endpoint, {
435
+ method: 'PUT',
436
+ body: JSON.stringify(data)
437
+ });
438
+ }
439
+
440
+ async delete(endpoint) {
441
+ return this.request(endpoint, { method: 'DELETE' });
442
+ }
443
+ }
444
+
445
+ // Global instance
446
+ window.api = new ApiClient('https://api.ejemplo.com');
447
+ ```
448
+
449
+ ### Usage in the Application
450
+
451
+ #### Global and Page-specific Scripts (multipage)
452
+
453
+ In multipage applications, you can add global scripts to the App and page-specific scripts to each Page:
454
+
455
+ ```python
456
+ from dars.scripts.script import InlineScript
457
+ from dars.components.basic import Page, Button, Text
458
+
459
+ home = Page(
460
+ Text("Inicio"),
461
+ Button("Ir a About", id="btn-about")
462
+ )
463
+ home.add_script(InlineScript("""
464
+ document.addEventListener('DOMContentLoaded', function() {
465
+ var btn = document.getElementById('btn-about');
466
+ if (btn) btn.onclick = () => window.location.href = 'about.html';
467
+ });
468
+ """))
469
+
470
+ about = Page(
471
+ Text("Sobre Nosotros"),
472
+ Button("Volver", id="btn-home")
473
+ )
474
+ about.add_script(InlineScript("""
475
+ document.addEventListener('DOMContentLoaded', function() {
476
+ var btn = document.getElementById('btn-home');
477
+ if (btn) btn.onclick = () => window.location.href = 'index.html';
478
+ });
479
+ """))
480
+
481
+ # Global script
482
+ app.add_script(InlineScript("console.log('Script global para todas las páginas');"))
483
+ ```
484
+
485
+ When exporting, each page will have its own JS file combining global scripts and page-specific scripts.
486
+
487
+ ```python
488
+ from dars.scripts.script import FileScript
489
+
490
+ # Load multiple scripts
491
+ app.add_script(FileScript("./scripts/utils.js"))
492
+ app.add_script(FileScript("./scripts/api.js"))
493
+ app.add_script(FileScript("./scripts/validaciones.js"))
494
+ ```
495
+
496
+ ## Component Integration
497
+
498
+ ### Connecting Scripts to Components
499
+
500
+ ```python
501
+ from dars.core.app import App
502
+ from dars.components.basic.button import Button
503
+ from dars.components.basic.input import Input
504
+ from dars.components.basic.container import Container
505
+ from dars.scripts.script import InlineScript
506
+
507
+ # Create components with specific IDs
508
+ formulario = Container(
509
+ id="formulario-contacto",
510
+ children=[
511
+ Input(
512
+ id="campo-nombre",
513
+ placeholder="Nombre",
514
+ required=True
515
+ ),
516
+ Input(
517
+ id="campo-email",
518
+ placeholder="Email",
519
+ input_type="email",
520
+ required=True
521
+ ),
522
+ Button(
523
+ id="boton-enviar",
524
+ text="Enviar"
525
+ )
526
+ ]
527
+ )
528
+
529
+ # Script that interacts with components
530
+ script_formulario = InlineScript("""
531
+ document.addEventListener(\'DOMContentLoaded\', function() {
532
+ const formulario = document.getElementById(\'formulario-contacto\');
533
+ const campoNombre = document.getElementById(\'campo-nombre\');
534
+ const campoEmail = document.getElementById(\'campo-email\');
535
+ const botonEnviar = document.getElementById(\'boton-enviar\');
536
+
537
+ // Real-time validation
538
+ campoNombre.addEventListener(\'input\', function() {
539
+ validarNombre(this.value);
540
+ });
541
+
542
+ campoEmail.addEventListener(\'input\', function() {
543
+ validarEmail(this.value);
544
+ });
545
+
546
+ // Handle form submission
547
+ botonEnviar.addEventListener(\'click\', function(e) {
548
+ e.preventDefault();
549
+ enviarFormulario();
550
+ });
551
+
552
+ function validarNombre(nombre) {
553
+ const esValido = nombre.length >= 2;
554
+ campoNombre.style.borderColor = esValido ? \'#28a745\' : \'#dc3545\';
555
+ return esValido;
556
+ }
557
+
558
+ function validarEmail(email) {
559
+ const regex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
560
+ const esValido = regex.test(email);
561
+ campoEmail.style.borderColor = esValido ? \'#28a745\' : \'#dc3545\';
562
+ return esValido;
563
+ }
564
+
565
+ function enviarFormulario() {
566
+ const nombre = campoNombre.value;
567
+ const email = campoEmail.value;
568
+
569
+ if (validarNombre(nombre) && validarEmail(email)) {
570
+ // Simular envío
571
+ botonEnviar.textContent = \'Enviando...\';
572
+ botonEnviar.disabled = true;
573
+
574
+ setTimeout(() => {
575
+ alert(\'Formulario enviado correctamente\');
576
+ campoNombre.value = \'\';
577
+ campoEmail.value = \'\';
578
+ botonEnviar.textContent = \'Enviar\';
579
+ botonEnviar.disabled = false;
580
+ }, 2000);
581
+ } else {
582
+ alert(\'Por favor, corrige los errores en el formulario\');
583
+ }
584
+ }
585
+ });
586
+ """)
587
+
588
+ # Add to the application
589
+ app = App(title="Form with Script")
590
+ app.set_root(form)
591
+ app.add_script(form_script)
592
+
593
+
@@ -0,0 +1,57 @@
1
+ # State management in Dars (dState, cState, goto, mods)
2
+
3
+ This document describes the new state system available in Dars 1.1.9.
4
+
5
+ - dState(name, component|id, states): declares a state tied to a DOM target (component id).
6
+ - state(idx=None, goto=None, cComp=False, render=None): triggers a state change from Python by producing a JS inline script.
7
+ - cState(idx, mods=[...]): declares rules to execute when entering a state.
8
+ - Mod helpers: inc, dec, set, toggle_class, append_text, prepend_text.
9
+ - goto: absolute (e.g. 2) or relative ("+1", "-1") state jumps.
10
+
11
+ ## Quick start
12
+ ```python
13
+ from dars.all import *
14
+ from dars.core.state import dState, Mod
15
+
16
+ app = App(title="State Demo")
17
+ label = Text("0", id="Counter")
18
+ st = dState("counter", component=label, states=[0,1,2,3])
19
+
20
+ # Rules on state entry
21
+ st.cState(1, mods=[Mod.inc(label, prop='text', by=1)])
22
+ st.cState(2, mods=[Mod.dec(label, prop='text', by=1)])
23
+ st.cState(3, mods=[Mod.toggle_class(label, name='highlight', on=None)])
24
+
25
+ # Buttons to navigate
26
+ next_btn = Button("Next", on_click=st.state(goto='+1'))
27
+ prev_btn = Button("Prev", on_click=st.state(goto='-1'))
28
+ ```
29
+
30
+ ## Full HTML replacement (custom components)
31
+ If you need full HTML replacement on state change:
32
+ ```python
33
+ swap_btn = Button(
34
+ "Swap",
35
+ on_click=st.state(2, cComp=True, render=label.mod(text="SWAPPED"))
36
+ )
37
+ ```
38
+ `render` accepts:
39
+ - A DeferredAttr produced by `component.mod(...)` or `component.attr(..., defer=True)`.
40
+ - A Component instance (will be rendered to HTML at event time).
41
+ - A raw HTML string.
42
+
43
+ ## Runtime behavior
44
+ - At export time, state declarations are embedded in the page as a bootstrap JSON.
45
+ - The runtime (dars.min.js) registers states with: id, states, current index, and optional rules.
46
+ - `change({...})` resolves `goto`, updates `current`, applies `rules[<state>].mods` and optional `rules[<state>].goto` (single hop), then dispatches a `CustomEvent('dars:state', ...)`.
47
+
48
+ ## Mod operations
49
+ - inc/dec(target, prop='text', by=1): increments or decrements a numeric value (textContent by default).
50
+ - set(target, **attrs): sets attributes; `text` sets textContent; `html` sets innerHTML; other keys map to element attributes.
51
+ - toggle_class(target, name, on=None): toggles a class; when `on` is True/False, forces add/remove.
52
+ - append_text / prepend_text: concatenates to textContent.
53
+
54
+ ## Best practices
55
+ - Keep the label text purely numeric if you plan to use `inc/dec` on `text`.
56
+ - Use `goto` in rules to avoid infinite accumulation when staying at the same state.
57
+ - Prefer `mods` for small changes; use `cComp=True` only when you need full HTML replacement.
File without changes