@dogiloki/artha-js 1.2.1 → 1.3.0

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.
package/README.md CHANGED
@@ -1,687 +1,818 @@
1
- # Artha JS
2
-
3
- Mini librería para construir interfaces HTML reactivas con Web Components, peticiones XHR, cola de tareas y mensajes visuales sin depender de frameworks.
4
-
5
- `Artha JS` expone un conjunto pequeño de utilidades y componentes personalizados pensados para:
6
-
7
- - enviar formularios por XMLHttpRequest
8
- - renderizar listas o bloques desde respuestas JSON
9
- - mostrar loaders y mensajes de estado
10
- - coordinar eventos globales entre componentes
11
-
12
- ## Contenido
13
-
14
- - [¿Qué incluye?](#que-incluye)
15
- - [Instalación](#instalación)
16
- - [Inicio rápido](#inicio-rápido)
17
- - [Exportaciones](#exportaciones)
18
- - [Componentes](#componentes)
19
- - [Core](#core)
20
- - [Flujo de respuesta esperado](#flujo-de-respuesta-esperado)
21
- - [Eventos útiles](#eventos-útiles)
22
- - [Desarrollo](#desarrollo)
23
- - [Licencia](#licencia)
24
-
25
- ## ¿Qué incluye?
26
-
27
- La librería exporta estas piezas:
28
-
29
- - `Util`: helpers de DOM, formato y utilidades generales
30
- - `EventBus`: bus global de eventos
31
- - `TaskQueue`: cola simple para evitar tareas duplicadas y coordinar estados
32
- - `XHR`: wrapper ligero sobre `XMLHttpRequest`
33
- - `ArthaMessage`: componente para mostrar mensajes de estado
34
- - `ArthaLoader`: componente visual de carga
35
- - `ArthaContainer`: componente para cargar y renderizar datos
36
- - `ArthaForm`: formulario con envío asíncrono
37
-
38
- Al importar `dist/artha.min.js`, la librería registra automáticamente estos custom elements:
39
-
40
- - `artha-container`
41
- - `artha-form`
42
- - `artha-message`
43
- - `artha-loader`
44
-
45
- ## Instalación
46
-
47
- ### Desde npm
48
-
49
- ```bash
50
- npm install @dogiloki/artha-js
51
- ```
52
-
53
- ### Importando el bundle
54
-
55
- ```js
56
- import {
57
- XHR,
58
- EventBus,
59
- TaskQueue,
60
- Util,
61
- ArthaForm,
62
- ArthaContainer,
63
- ArthaMessage,
64
- ArthaLoader
65
- } from "@dogiloki/artha-js/dist/artha.min.js";
66
- ```
67
-
68
- ### Importando estilos
69
-
70
- Si tu bundler soporta CSS desde dependencias:
71
-
72
- ```js
73
- import "@dogiloki/artha-js/dist/artha.min.css";
74
- ```
75
-
76
- O bien desde HTML:
77
-
78
- ```html
79
- <link rel="stylesheet" href="./node_modules/@dogiloki/artha-js/dist/artha.min.css">
80
- ```
81
-
82
- ## Inicio rápido
83
-
84
- Ejemplo mínimo con un formulario y un contenedor que carga datos remotos:
85
-
86
- ```html
87
- <!DOCTYPE html>
88
- <html lang="es">
89
- <head>
90
- <meta charset="UTF-8">
91
- <meta name="csrf-token" content="TOKEN_OPCIONAL">
92
- <link rel="stylesheet" href="./dist/artha.min.css">
93
- </head>
94
- <body>
95
- <artha-form
96
- id="user-form"
97
- action="/api/user"
98
- method="POST">
99
- <input type="text" name="name" placeholder="Nombre">
100
- <input type="email" name="email" placeholder="Correo">
101
- <button type="submit">Guardar</button>
102
- </artha-form>
103
-
104
- <artha-container
105
- id="users"
106
- action="/api/users"
107
- method="GET"
108
- template="user-template">
109
- </artha-container>
110
-
111
- <template id="user-template">
112
- <article>
113
- <h3 data-wire="name"></h3>
114
- <p data-wire="email"></p>
115
- <small data-wire="id"></small>
116
- </article>
117
- </template>
118
-
119
- <script type="module">
120
- import "./dist/artha.min.js";
121
- </script>
122
- </body>
123
- </html>
124
- ```
125
-
126
- ## Exportaciones
127
-
128
- ```js
129
- import {
130
- Util,
131
- EventBus,
132
- TaskQueue,
133
- XHR,
134
- ArthaMessage,
135
- ArthaLoader,
136
- ArthaContainer,
137
- ArthaForm
138
- } from "./dist/artha.min.js";
139
- ```
140
-
141
- Al cargar el módulo también se emiten dos eventos globales:
142
-
143
- - `artha:before-register`
144
- - `artha:after-register`
145
-
146
- Esto sirve, por ejemplo, para personalizar la transformación de respuestas antes de registrar los componentes:
147
-
148
- ```js
149
- import { EventBus, XHR } from "./dist/artha.min.js";
150
-
151
- EventBus.on("artha:before-register", () => {
152
- XHR.defaults.transformResponse = (xhr) => ({
153
- data: xhr.response,
154
- message: null,
155
- errors: null,
156
- status: "success"
157
- });
158
- });
159
- ```
160
-
161
- ## Componentes
162
-
163
- ### `artha-message`
164
-
165
- Componente para mostrar mensajes visuales.
166
-
167
- ### Tipos soportados
168
-
169
- - `info`
170
- - `success`
171
- - `warning`
172
- - `error`
173
-
174
- ### Ejemplo
175
-
176
- ```html
177
- <artha-message id="feedback"></artha-message>
178
- ```
179
-
180
- ```js
181
- const message = document.getElementById("feedback");
182
-
183
- message.info("Cargando información...");
184
- message.success("Guardado correctamente");
185
- message.warning("Faltan campos por revisar");
186
- message.error("Ocurrió un error");
187
- message.hidden();
188
- ```
189
-
190
- ### API pública
191
-
192
- - `show(message, type)`
193
- - `info(message)`
194
- - `success(message)`
195
- - `warning(message)`
196
- - `error(message)`
197
- - `hidden()`
198
-
199
- ### `artha-loader`
200
-
201
- Loader visual para estados de carga.
202
-
203
- ### Atributos
204
-
205
- - `type`: tipo de loader. Default: `ring`
206
- - `text`: texto mostrado debajo del loader. Default: `Petición en proceso...`
207
-
208
- ### Tipos disponibles
209
-
210
- - `ring`
211
- - `dots`
212
- - `bar`
213
- - `wave`
214
-
215
- Nota: en la implementación actual `bar` y `wave` reutilizan la misma clase visual que `dots`.
216
-
217
- ### Ejemplo
218
-
219
- ```html
220
- <artha-loader type="ring" text="Cargando usuarios"></artha-loader>
221
- <artha-loader type="dots" text="Procesando"></artha-loader>
222
- ```
223
-
224
- ### `artha-form`
225
-
226
- Formulario asíncrono basado en `XMLHttpRequest`.
227
-
228
- ### Comportamiento
229
-
230
- - intercepta el evento `submit`
231
- - valida los campos con `checkValidity()`
232
- - envía los datos por XHR
233
- - muestra mensajes con `artha-message`
234
- - rellena campos automáticamente si la respuesta trae `data`
235
-
236
- ### Atributos útiles
237
-
238
- - `action`: endpoint del formulario
239
- - `method`: método HTTP
240
- - `response-type`: tipo de respuesta del XHR. Default: `json`
241
- - `disable-submit`: impide el envío automático al presionar Enter
242
- - `message-target`: selector interno para localizar el mensaje asociado
243
- - `id`: usado para identificar la tarea en `TaskQueue`
244
-
245
- ### Ejemplo
246
-
247
- ```html
248
- <artha-form id="profile-form" action="/api/profile" method="POST">
249
- <artha-message></artha-message>
250
- <input type="text" name="name" required>
251
- <input type="email" name="email" required>
252
- <label>
253
- <input type="checkbox" name="active">
254
- Activo
255
- </label>
256
- <button type="submit">Guardar</button>
257
- <button type="reset">Limpiar</button>
258
- </artha-form>
259
- ```
260
-
261
- ### API pública
262
-
263
- - `submit()`
264
- - `reset(resetMessage = true)`
265
- - `resetMessage()`
266
- - `checkValidity()`
267
- - `loadInputs(selector = "input,select,textarea")`
268
- - `fillFromJson(json, reset = true)`
269
- - `getValue(name)`
270
- - `input(name)`
271
-
272
- ### Eventos emitidos
273
-
274
- - `load`: se dispara cuando XHR termina de cargar
275
- - `resolve`: se dispara cuando la respuesta fue aceptada y procesada
276
- - `component-ready`: heredado de `BaseComponent`
277
-
278
- ### `artha-container`
279
-
280
- Componente para cargar, renderizar y refrescar datos remotos, o actualizar vistas existentes.
281
-
282
- ### Casos de uso
283
-
284
- - listados
285
- - tarjetas
286
- - tablas simples
287
- - bloques con plantillas HTML
288
- - selección simple o múltiple
289
- - refresco desde eventos globales
290
-
291
- ### Atributos útiles
292
-
293
- - `action`: endpoint a consultar
294
- - `method`: método HTTP. Default: `GET`
295
- - `template`: id de un `<template>` o referencia configurada en el componente
296
- - `pagination`: valor configurado pero no aplicado directamente en la clase actual. Default: `10`
297
- - `message`: referencia al mensaje asociado
298
- - `message-target`: selector interno alternativo para localizar un `artha-message`
299
- - `searcher`: activa el comportamiento de búsqueda si existe un `input-search`
300
- - `selectable`: permite seleccionar items
301
- - `multiple`: permite múltiples selecciones
302
- - `refresh-on`: nombres de eventos del `EventBus`, separados por coma
303
- - `id`: identificador del contenedor
304
-
305
- ### Ejemplo con plantilla
306
-
307
- ```html
308
- <artha-container
309
- id="users"
310
- action="/api/users"
311
- method="GET"
312
- template="user-card"
313
- selectable
314
- multiple
315
- refresh-on="users:reload,users:updated">
316
- <artha-message></artha-message>
317
- </artha-container>
318
-
319
- <template id="user-card">
320
- <article class="user-card">
321
- <h3 data-wire="name"></h3>
322
- <p data-wire="email"></p>
323
- <span data-wire="active:boolean"></span>
324
- </article>
325
- </template>
326
- ```
327
-
328
- ### API pública
329
-
330
- - `refresh(search = null)`: vuelve a pedir los datos remotos
331
- - `refreshWithData(data)`: actualiza un item ya renderizado por `data.id`
332
- - `render(results, refresh = false, refreshChildren = true)`
333
- - `renderItem(data, refreshChildren = true, update = null)`
334
- - `reset()`: limpia la selección
335
- - `selection()`: devuelve el store de selección
336
- - propiedad `value`: ids seleccionados
337
-
338
- ### Selección
339
-
340
- Si `selectable` está activo:
341
-
342
- - `container.value` devuelve el id seleccionado
343
- - si también `multiple` está activo, devuelve un arreglo de ids
344
- - `reset()` limpia la selección actual
345
-
346
- ### Eventos emitidos
347
-
348
- - `load`
349
- - `resolve`
350
- - `dynamic-content-loaded`
351
- - `item-rendered`
352
- - `item-selected`
353
- - `item-deselected`
354
- - `component-ready`
355
-
356
- ### Sistema `data-wire`
357
-
358
- El renderizado se basa en atributos `data-wire` dentro de la plantilla.
359
-
360
- Formato general:
361
-
362
- ```html
363
- data-wire="ruta"
364
- data-wire="ruta:atributo"
365
- data-wire="ruta:atributo:append"
366
- data-wire="ruta:boolean"
367
- data-wire="ruta:boolean:chooser"
368
- ```
369
-
370
- ### Ejemplos de `data-wire`
371
-
372
- Texto simple:
373
-
374
- ```html
375
- <span data-wire="name"></span>
376
- ```
377
-
378
- Propiedad anidada:
379
-
380
- ```html
381
- <span data-wire="user.email"></span>
382
- ```
383
-
384
- Append sobre contenido actual:
385
-
386
- ```html
387
- <span data-wire="price:textContent:append">$ </span>
388
- ```
389
-
390
- Booleano como check o cross:
391
-
392
- ```html
393
- <span data-wire="active:boolean"></span>
394
- ```
395
-
396
- Booleano con selección por plantilla:
397
-
398
- ```html
399
- <div data-wire="status:boolean:chooser">
400
- <template data-chooser-value="approved">
401
- <span>Aprobado</span>
402
- </template>
403
- <template data-chooser-value="rejected">
404
- <span>Rechazado</span>
405
- </template>
406
- <template data-chooser-default>
407
- <span>Pendiente</span>
408
- </template>
409
- </div>
410
- ```
411
-
412
- ### Renderizado de arreglos
413
-
414
- El componente soporta rutas terminadas en `[]` para iterar datos. Internamente, busca elementos marcados con `fillable` o `iterable`.
415
-
416
- Ejemplo conceptual:
417
-
418
- ```html
419
- <ul data-wire="tags[]">
420
- <li>
421
- <span fillable></span>
422
- </li>
423
- </ul>
424
- ```
425
-
426
- Nota: el flujo más sólido en la implementación actual es renderizar texto, booleanos y arreglos. Si quieres usar mapeos más avanzados, conviene probarlos primero en tu caso concreto.
427
-
428
- ## Core
429
-
430
- ### `XHR`
431
-
432
- Wrapper de `XMLHttpRequest` con callbacks y opciones centralizadas.
433
-
434
- ### Uso básico
435
-
436
- ```js
437
- import { XHR } from "./dist/artha.min.js";
438
-
439
- XHR.request({
440
- url: "/api/users",
441
- method: "GET",
442
- headers: {
443
- Accept: "application/json"
444
- },
445
- onData: (xhr, data) => {
446
- console.log("ok", data);
447
- },
448
- onError: (error) => {
449
- console.error("error", error);
450
- }
451
- });
452
- ```
453
-
454
- ### Opciones disponibles
455
-
456
- - `method`: default `GET`
457
- - `url`: URL final
458
- - `uri`: alternativa para construir `"/" + uri"`
459
- - `headers`: headers adicionales
460
- - `data`: datos del formulario
461
- - `query`: query params para GET
462
- - `files`: archivos o listas de archivos
463
- - `response_type`: default `json`
464
- - `with_credentials`: default `false`
465
- - `timeout`: default `0`
466
- - `retry`: reintenta en `error` o `timeout`
467
- - `retry_delay`: default `5000`
468
- - `transformResponse(xhr)`: transforma `xhr.response`
469
- - `onLoad(xhr)`
470
- - `onData(xhr, transformed)`
471
- - `onError(transformed)`
472
- - `onTimeout(transformed)`
473
- - `onProgress(event, loaded, total)`
474
- - `onAbort(transformed)`
475
- - `onAction(xhr)`
476
-
477
- ### Notas de comportamiento
478
-
479
- - si existe `<meta name="csrf-token">` o `<meta name="csrf_token">`, se envía como header `X-CSRF-Token`
480
- - para métodos distintos de `GET`, la librería envía `FormData`
481
- - también agrega `_method` dentro del `FormData`
482
- - si hay token CSRF, también agrega `csrf_token` al cuerpo
483
-
484
- ### `TaskQueue`
485
-
486
- Evita ejecutar dos tareas con el mismo id al mismo tiempo y centraliza el cierre de estados.
487
-
488
- ### Uso básico
489
-
490
- ```js
491
- import { TaskQueue } from "./dist/artha.min.js";
492
-
493
- const queue = TaskQueue.singleton();
494
-
495
- queue.loadTask("save-user", "Guardando usuario...", (task) => {
496
- setTimeout(() => {
497
- task.resolve({
498
- status: 200,
499
- response: JSON.stringify({
500
- status: "success",
501
- message: "Usuario guardado",
502
- data: { id: 1 }
503
- })
504
- });
505
- }, 500);
506
- }, {
507
- close: true
508
- });
509
- ```
510
-
511
- ### Defaults
512
-
513
- ```js
514
- TaskQueue.defaults = {
515
- title: "Petición en proceso...",
516
- close: false,
517
- message: null
518
- };
519
- ```
520
-
521
- ### Observaciones
522
-
523
- - cada tarea necesita un id único
524
- - si se repite el id mientras sigue activa, se cancela la nueva tarea
525
- - si se pasa un `ArthaMessage`, la cola actualiza sus estados visuales
526
- - `close: true` intenta cerrar el mensaje automáticamente al finalizar
527
-
528
- ### `EventBus`
529
-
530
- Bus global basado en `EventTarget`.
531
-
532
- ### Uso
533
-
534
- ```js
535
- import { EventBus } from "./dist/artha.min.js";
536
-
537
- const unsubscribe = EventBus.on("users:reload", (data) => {
538
- console.log("recargar", data);
539
- });
540
-
541
- EventBus.emit("users:reload", { source: "manual" });
542
- EventBus.emitAsync("users:reload", { source: "async" });
543
- unsubscribe();
544
- ```
545
-
546
- ### API pública
547
-
548
- - `EventBus.emit(name, data)`
549
- - `EventBus.emitAsync(name, data)`
550
- - `EventBus.on(name, callback)`
551
- - `EventBus.once(name, callback)`
552
- - `EventBus.onAny(callback)`
553
- - `EventBus.off(name, callback)`
554
- - `EventBus.clean(name)`
555
- - `EventBus.clearAll()`
556
-
557
- ### Debug
558
-
559
- ```js
560
- EventBus.debug = true;
561
- ```
562
-
563
- ### `Util`
564
-
565
- Utilidades generales.
566
-
567
- ### API pública
568
-
569
- - `Util.getMeta(name)`
570
- - `Util.getValueByPath(obj, path, defaultValue = null)`
571
- - `Util.modal(element, visible = -1)`
572
- - `Util.modalById(id, visible = -1)`
573
- - `Util.formatMoney(value, options = {})`
574
- - `Util.numberRandom(min, max)`
575
- - `Util.withinRange(value, min, max)`
576
- - `Util.createElement(type, value = null, options = {})`
577
-
578
- ### Ejemplos
579
-
580
- ```js
581
- Util.getMeta("csrf-token");
582
- Util.getValueByPath({ user: { name: "Ana" } }, "user.name");
583
- Util.modalById("panel", true);
584
- Util.formatMoney("1234.5");
585
- Util.numberRandom(1, 10);
586
- Util.withinRange(204, 200, 299);
587
- ```
588
-
589
- ## Flujo de respuesta esperado
590
-
591
- `ArthaForm` y `ArthaContainer` funcionan mejor cuando el backend responde con una estructura parecida a esta:
592
-
593
- ```json
594
- {
595
- "status": "success",
596
- "message": "Operación completada",
597
- "data": []
598
- }
599
- ```
600
-
601
- También soporta errores con este formato:
602
-
603
- ```json
604
- {
605
- "status": "error",
606
- "message": "No se pudo completar la operación",
607
- "errors": {
608
- "email": ["El correo ya existe"]
609
- }
610
- }
611
- ```
612
-
613
- Si tu API responde con otro formato, puedes adaptar la salida usando `XHR.defaults.transformResponse`.
614
-
615
- ## Eventos útiles
616
-
617
- ### Eventos de componentes
618
-
619
- - `component-ready`
620
- - `load`
621
- - `resolve`
622
- - `dynamic-content-loaded`
623
- - `item-rendered`
624
- - `item-selected`
625
- - `item-deselected`
626
-
627
- ### Eventos globales de Artha
628
-
629
- - `artha:before-register`
630
- - `artha:after-register`
631
-
632
- ### Ejemplo de refresco entre componentes
633
-
634
- ```js
635
- import { EventBus } from "./dist/artha.min.js";
636
-
637
- EventBus.emit("users:updated", { id: 3, name: "Nuevo nombre" });
638
- EventBus.emit("users:reload");
639
- ```
640
-
641
- Y en el contenedor:
642
-
643
- ```html
644
- <artha-container
645
- action="/api/users"
646
- template="user-template"
647
- refresh-on="users:reload,users:updated">
648
- </artha-container>
649
- ```
650
-
651
- ## Desarrollo
652
-
653
- ### Instalar dependencias
654
-
655
- ```bash
656
- npm install
657
- ```
658
-
659
- ### Desarrollo
660
-
661
- Compilar CSS en modo watch:
662
-
663
- ```bash
664
- npm run dev:css
665
- ```
666
-
667
- Levantar servidor local:
668
-
669
- ```bash
670
- npm run dev:server
671
- ```
672
-
673
- Ejecutar ambos:
674
-
675
- ```bash
676
- npm run dev
677
- ```
678
-
679
- ### Build
680
-
681
- ```bash
682
- npm run build
683
- ```
684
-
685
- ## Licencia
686
-
687
- MIT. Consulta [`LICENSE`](./LICENSE).
1
+ # Artha JS
2
+
3
+ Mini librería para construir interfaces HTML reactivas con Web Components, peticiones XHR, cola de tareas y mensajes visuales sin depender de frameworks.
4
+
5
+ `Artha JS` expone un conjunto pequeño de utilidades y componentes personalizados pensados para:
6
+
7
+ - enviar formularios por XMLHttpRequest
8
+ - renderizar listas o bloques desde respuestas JSON
9
+ - mostrar loaders y mensajes de estado
10
+ - buscar con debounce sobre contenedores remotos
11
+ - coordinar eventos globales entre componentes
12
+
13
+ ## Contenido
14
+
15
+ - [¿Qué incluye?](#que-incluye)
16
+ - [Instalación](#instalación)
17
+ - [Inicio rápido](#inicio-rápido)
18
+ - [Exportaciones](#exportaciones)
19
+ - [Componentes](#componentes)
20
+ - [Core](#core)
21
+ - [Flujo de respuesta esperado](#flujo-de-respuesta-esperado)
22
+ - [Eventos útiles](#eventos-útiles)
23
+ - [Desarrollo](#desarrollo)
24
+ - [Licencia](#licencia)
25
+
26
+ ## ¿Qué incluye?
27
+
28
+ La librería exporta estas piezas:
29
+
30
+ - `Util`: helpers de DOM, formato y utilidades generales
31
+ - `EventBus`: bus global de eventos
32
+ - `TaskQueue`: cola simple para evitar tareas duplicadas y coordinar estados
33
+ - `XHR`: wrapper ligero sobre `XMLHttpRequest`
34
+ - `ArthaMessage`: componente para mostrar mensajes de estado
35
+ - `ArthaLoader`: componente visual de carga
36
+ - `ArthaContainer`: componente para cargar y renderizar datos
37
+ - `ArthaForm`: formulario con envío asíncrono
38
+ - `InputSearch`: componente de búsqueda con debounce
39
+
40
+ Al importar `dist/artha.min.js`, la librería registra automáticamente estos custom elements:
41
+
42
+ - `artha-container`
43
+ - `artha-form`
44
+ - `artha-message`
45
+ - `artha-loader`
46
+ - `input-search`
47
+
48
+ ## Instalación
49
+
50
+ ### Desde npm
51
+
52
+ ```bash
53
+ npm install @dogiloki/artha-js
54
+ ```
55
+
56
+ ### Importando el bundle
57
+
58
+ ```js
59
+ import {
60
+ XHR,
61
+ EventBus,
62
+ TaskQueue,
63
+ Util,
64
+ ArthaForm,
65
+ ArthaContainer,
66
+ ArthaMessage,
67
+ ArthaLoader,
68
+ InputSearch
69
+ } from "@dogiloki/artha-js/dist/artha.min.js";
70
+ ```
71
+
72
+ ### Importando estilos
73
+
74
+ Si tu bundler soporta CSS desde dependencias:
75
+
76
+ ```js
77
+ import "@dogiloki/artha-js/dist/artha.min.css";
78
+ ```
79
+
80
+ O bien desde HTML:
81
+
82
+ ```html
83
+ <link rel="stylesheet" href="./node_modules/@dogiloki/artha-js/dist/artha.min.css">
84
+ ```
85
+
86
+ ## Inicio rápido
87
+
88
+ Ejemplo mínimo con formulario, contenedor remoto y búsqueda:
89
+
90
+ ```html
91
+ <!DOCTYPE html>
92
+ <html lang="es">
93
+ <head>
94
+ <meta charset="UTF-8">
95
+ <meta name="csrf-token" content="TOKEN_OPCIONAL">
96
+ <link rel="stylesheet" href="./dist/artha.min.css">
97
+ </head>
98
+ <body>
99
+ <artha-form
100
+ id="user-form"
101
+ action="/api/user"
102
+ method="POST">
103
+ <input type="text" name="name" placeholder="Nombre">
104
+ <input type="email" name="email" placeholder="Correo">
105
+ <button type="submit">Guardar</button>
106
+ </artha-form>
107
+
108
+ <artha-container
109
+ id="users"
110
+ action="/api/users"
111
+ method="GET"
112
+ template="user-template"
113
+ searcher
114
+ pagination="10">
115
+ </artha-container>
116
+
117
+ <template id="user-template">
118
+ <article>
119
+ <h3 data-wire="name"></h3>
120
+ <p data-wire="email"></p>
121
+ <small data-wire="id"></small>
122
+ </article>
123
+ </template>
124
+
125
+ <script type="module">
126
+ import "./dist/artha.min.js";
127
+ </script>
128
+ </body>
129
+ </html>
130
+ ```
131
+
132
+ ## Exportaciones
133
+
134
+ ```js
135
+ import {
136
+ Util,
137
+ EventBus,
138
+ TaskQueue,
139
+ XHR,
140
+ ArthaMessage,
141
+ ArthaLoader,
142
+ ArthaContainer,
143
+ ArthaForm,
144
+ InputSearch
145
+ } from "./dist/artha.min.js";
146
+ ```
147
+
148
+ Al cargar el módulo también se emiten dos eventos globales:
149
+
150
+ - `artha:before-register`
151
+ - `artha:after-register`
152
+
153
+ Esto sirve, por ejemplo, para personalizar la transformación de respuestas antes de registrar los componentes:
154
+
155
+ ```js
156
+ import { EventBus, XHR } from "./dist/artha.min.js";
157
+
158
+ EventBus.on("artha:before-register", () => {
159
+ XHR.defaults.transformResponse = (xhr) => ({
160
+ data: xhr.response,
161
+ message: null,
162
+ errors: null,
163
+ status: "success"
164
+ });
165
+ });
166
+ ```
167
+
168
+ Importante: si quieres cambiar `XHR.defaults.transformResponse`, hazlo antes de que los componentes se registren. La forma recomendada es usar el evento `artha:before-register`.
169
+
170
+ ## Componentes
171
+
172
+ ### `artha-message`
173
+
174
+ Componente para mostrar mensajes visuales.
175
+
176
+ #### Tipos soportados
177
+
178
+ - `info`
179
+ - `success`
180
+ - `warning`
181
+ - `error`
182
+
183
+ #### Ejemplo
184
+
185
+ ```html
186
+ <artha-message id="feedback"></artha-message>
187
+ ```
188
+
189
+ ```js
190
+ const message = document.getElementById("feedback");
191
+
192
+ message.info("Cargando información...");
193
+ message.success("Guardado correctamente");
194
+ message.warning("Faltan campos por revisar");
195
+ message.error("Ocurrió un error");
196
+ message.hidden();
197
+ ```
198
+
199
+ #### API pública
200
+
201
+ - `show(message, type)`
202
+ - `info(message)`
203
+ - `success(message)`
204
+ - `warning(message)`
205
+ - `error(message)`
206
+ - `hidden()`
207
+
208
+ ### `artha-loader`
209
+
210
+ Loader visual para estados de carga.
211
+
212
+ #### Atributos
213
+
214
+ - `type`: tipo de loader. Default: `ring`
215
+ - `text`: texto mostrado debajo del loader. Default: `Petición en proceso...`
216
+
217
+ #### Tipos disponibles
218
+
219
+ - `ring`
220
+ - `dots`
221
+ - `bar`
222
+ - `wave`
223
+
224
+ Nota: en la implementación actual `bar` y `wave` reutilizan la misma clase visual que `dots`.
225
+
226
+ #### Ejemplo
227
+
228
+ ```html
229
+ <artha-loader type="ring" text="Cargando usuarios"></artha-loader>
230
+ <artha-loader type="dots" text="Procesando"></artha-loader>
231
+ ```
232
+
233
+ ### `artha-form`
234
+
235
+ Formulario asíncrono basado en `XMLHttpRequest`.
236
+
237
+ #### Comportamiento
238
+
239
+ - intercepta el evento `submit`
240
+ - valida los campos con `checkValidity()`
241
+ - envía los datos por XHR
242
+ - muestra mensajes con `artha-message`
243
+ - rellena campos automáticamente si la respuesta trae `data`
244
+
245
+ #### Atributos útiles
246
+
247
+ - `action`: endpoint del formulario
248
+ - `method`: método HTTP
249
+ - `response-type`: tipo de respuesta del XHR. Default: `json`
250
+ - `disable-submit`: impide el envío automático al presionar Enter
251
+ - `message-target`: selector interno para localizar el mensaje asociado
252
+ - `id`: usado para identificar la tarea en `TaskQueue`
253
+
254
+ #### API pública
255
+
256
+ - `submit()`
257
+ - `reset(resetMessage = true)`
258
+ - `resetMessage()`
259
+ - `checkValidity()`
260
+ - `loadInputs(selector = "input,select,textarea")`
261
+ - `fillFromJson(json, reset = true)`
262
+ - `getValue(name)`
263
+ - `input(name)`
264
+
265
+ #### Eventos emitidos
266
+
267
+ - `load`
268
+ - `resolve`
269
+ - `component-ready`
270
+
271
+ ### `artha-container`
272
+
273
+ Componente para cargar, renderizar y refrescar datos remotos, o actualizar vistas existentes.
274
+
275
+ #### Casos de uso
276
+
277
+ - listados
278
+ - tarjetas
279
+ - tablas simples
280
+ - bloques con plantillas HTML
281
+ - selección simple o múltiple
282
+ - búsqueda con `input-search`
283
+ - paginación simple
284
+ - refresco desde eventos globales
285
+
286
+ #### Atributos útiles
287
+
288
+ - `action`: endpoint a consultar
289
+ - `method`: método HTTP. Default: `GET`
290
+ - `page`: página actual cuando hay paginación. Default: `1`
291
+ - `search`: criterio de búsqueda interno
292
+ - `response-type`: tipo de respuesta del XHR usado por el contenedor. Default: `json`
293
+ - `template`: id de un `<template>` o referencia configurada en el componente
294
+ - `pagination`: cantidad por página enviada en la query. Default: `10`
295
+ - `message`: referencia al mensaje asociado
296
+ - `message-target`: selector interno alternativo para localizar un `artha-message`
297
+ - `searcher`: crea internamente un `input-search` y lo conecta al contenedor
298
+ - `selectable`: permite seleccionar items
299
+ - `multiple`: permite múltiples selecciones
300
+ - `refresh-on`: nombres de eventos del `EventBus`, separados por coma
301
+ - `id`: identificador del contenedor
302
+
303
+ #### API pública
304
+
305
+ - `hasAction()`
306
+ - `hasPagination()`
307
+ - `refresh(search = null)`
308
+ - `refreshWithData(data)`
309
+ - `render(results, refresh = false, refreshChildren = true)`
310
+ - `renderItem(data, refreshChildren = true, update = null)`
311
+ - `nextPage()`
312
+ - `prevPage()`
313
+ - `goToPage(page)`
314
+ - `resetPagination(refresh = false)`
315
+ - `reset()`
316
+ - `selection()`
317
+ - propiedad `value`
318
+
319
+ #### Selección
320
+
321
+ Si `selectable` está activo:
322
+
323
+ - `container.value` devuelve el id seleccionado
324
+ - si también `multiple` está activo, devuelve un arreglo de ids
325
+ - `reset()` limpia la selección actual
326
+
327
+ #### Búsqueda
328
+
329
+ Si `searcher` está activo, `artha-container` crea un `<input-search>` interno y escucha:
330
+
331
+ - `search`: actualiza `search`, ejecuta `refresh()` y reinicia `page` a `1`
332
+ - `cancel-search`: aborta la petición XHR activa si existe
333
+
334
+ #### Paginación
335
+
336
+ Si el contenedor tiene el atributo `pagination`, enviará estos parámetros en la query:
337
+
338
+ - `pagination`
339
+ - `page`
340
+
341
+ Ejemplo:
342
+
343
+ ```html
344
+ <artha-container
345
+ id="users"
346
+ action="/api/users"
347
+ template="user-card"
348
+ pagination="10"
349
+ searcher>
350
+ </artha-container>
351
+ ```
352
+
353
+ Y desde JavaScript:
354
+
355
+ ```js
356
+ const container = document.getElementById("users");
357
+
358
+ container.nextPage();
359
+ container.prevPage();
360
+ container.goToPage(3);
361
+ container.resetPagination(true);
362
+ ```
363
+
364
+ #### Eventos emitidos
365
+
366
+ - `load`
367
+ - `resolve`
368
+ - `dynamic-content-loaded`
369
+ - `item-rendered`
370
+ - `item-selected`
371
+ - `item-deselected`
372
+ - `component-ready`
373
+
374
+ #### Sistema `data-wire`
375
+
376
+ El renderizado se basa en atributos `data-wire` dentro de la plantilla.
377
+
378
+ Formato general:
379
+
380
+ ```html
381
+ data-wire="ruta"
382
+ data-wire="ruta:atributo"
383
+ data-wire="ruta:atributo:append"
384
+ data-wire="ruta:boolean"
385
+ data-wire="ruta:boolean:chooser"
386
+ ```
387
+
388
+ #### Ejemplos de `data-wire`
389
+
390
+ Texto simple:
391
+
392
+ ```html
393
+ <span data-wire="name"></span>
394
+ ```
395
+
396
+ Propiedad anidada:
397
+
398
+ ```html
399
+ <span data-wire="user.email"></span>
400
+ ```
401
+
402
+ Append sobre contenido actual:
403
+
404
+ ```html
405
+ <span data-wire="price:textContent:append">$ </span>
406
+ ```
407
+
408
+ Booleano como check o cross:
409
+
410
+ ```html
411
+ <span data-wire="active:boolean"></span>
412
+ ```
413
+
414
+ Booleano con selección por plantilla:
415
+
416
+ ```html
417
+ <div data-wire="status:boolean:chooser">
418
+ <template data-chooser-value="approved">
419
+ <span>Aprobado</span>
420
+ </template>
421
+ <template data-chooser-value="rejected">
422
+ <span>Rechazado</span>
423
+ </template>
424
+ <template data-chooser-default>
425
+ <span>Pendiente</span>
426
+ </template>
427
+ </div>
428
+ ```
429
+
430
+ #### Renderizado de arreglos
431
+
432
+ El componente soporta rutas terminadas en `[]` para iterar datos. Internamente, busca elementos marcados con `fillable` o `iterable`.
433
+
434
+ Ejemplo conceptual:
435
+
436
+ ```html
437
+ <ul data-wire="tags[]">
438
+ <li>
439
+ <span fillable></span>
440
+ </li>
441
+ </ul>
442
+ ```
443
+
444
+ Nota: el flujo más sólido en la implementación actual es renderizar texto, booleanos y arreglos. Si quieres usar mapeos más avanzados, conviene probarlos primero en tu caso concreto.
445
+
446
+ ### `input-search`
447
+
448
+ Componente de búsqueda con debounce pensado para integrarse con `artha-container`.
449
+
450
+ #### Atributos
451
+
452
+ - `delay`: milisegundos de espera antes de emitir la búsqueda. Default: `300`
453
+ - `text`: placeholder del input. Default: `Buscar`
454
+
455
+ #### API pública
456
+
457
+ - `search()`
458
+
459
+ #### Eventos emitidos
460
+
461
+ - `search`: emite `{ query }`
462
+ - `cancel-search`: emite `{ query }` cuando se cancela una búsqueda en cola
463
+
464
+ #### Ejemplo
465
+
466
+ ```html
467
+ <input-search delay="300" text="Buscar usuarios"></input-search>
468
+ ```
469
+
470
+ ```js
471
+ const search = document.querySelector("input-search");
472
+
473
+ search.addEventListener("search", (evt) => {
474
+ console.log(evt.detail.query);
475
+ });
476
+ ```
477
+
478
+ ## Core
479
+
480
+ ### `BaseComponent`
481
+
482
+ Clase base para los custom elements de la librería. Gestiona atributos, propiedades, defaults, booleanos, referencias a elementos y eventos de cambio.
483
+
484
+ #### Opciones de configuración
485
+
486
+ - `booleans`: lista de props booleanas
487
+ - `element_refs`: props que apuntan a elementos del DOM por atributo `id` o por referencia en memoria
488
+ - `defaults`: valores por defecto
489
+ - `resolvers`: getter/setter personalizado por prop
490
+ - `reflect`: permite indicar props que no deben reflejarse como atributos HTML
491
+
492
+ #### `reflect`
493
+
494
+ Cuando una prop se define con `reflect: false`, `BaseComponent` la guarda en memoria y no usa `setAttribute()` ni `getAttribute()`.
495
+
496
+ Ejemplo conceptual:
497
+
498
+ ```js
499
+ super(["search", "page"], {
500
+ reflect: {
501
+ search: false
502
+ }
503
+ });
504
+ ```
505
+
506
+ Esto es útil para estado interno que no conviene escribir en el DOM.
507
+
508
+ ### `XHR`
509
+
510
+ Wrapper de `XMLHttpRequest` con callbacks y opciones centralizadas.
511
+
512
+ #### Uso básico
513
+
514
+ ```js
515
+ import { XHR } from "./dist/artha.min.js";
516
+
517
+ XHR.request({
518
+ url: "/api/users",
519
+ method: "GET",
520
+ headers: {
521
+ Accept: "application/json"
522
+ },
523
+ onData: (xhr, data) => {
524
+ console.log("ok", data);
525
+ },
526
+ onError: (error) => {
527
+ console.error("error", error);
528
+ }
529
+ });
530
+ ```
531
+
532
+ #### Opciones disponibles
533
+
534
+ - `method`: default `GET`
535
+ - `url`: URL final
536
+ - `uri`: alternativa para construir `"/" + uri"`
537
+ - `headers`: headers adicionales
538
+ - `data`: datos del formulario
539
+ - `query`: query params para GET
540
+ - `files`: archivos o listas de archivos
541
+ - `response_type`: default `json`
542
+ - `with_credentials`: default `false`
543
+ - `timeout`: default `0`
544
+ - `retry`: reintenta en `error` o `timeout`
545
+ - `retry_delay`: default `5000`
546
+ - `transformResponse(xhr)`: transforma `xhr.response`
547
+ - `onLoad(xhr)`
548
+ - `onData(xhr, transformed)`
549
+ - `onError(transformed)`
550
+ - `onTimeout(transformed)`
551
+ - `onProgress(event, loaded, total)`
552
+ - `onAbort(transformed)`
553
+ - `onAction(xhr)`
554
+
555
+ #### Notas de comportamiento
556
+
557
+ - si existe `<meta name="csrf-token">` o `<meta name="csrf_token">`, se envía como header `X-CSRF-Token`
558
+ - para métodos distintos de `GET`, la librería envía `FormData`
559
+ - también agrega `_method` dentro del `FormData`
560
+ - si hay token CSRF, también agrega `csrf_token` al cuerpo
561
+
562
+ #### Ejemplo con `transformResponse`
563
+
564
+ Si tu API no responde con la forma esperada por `ArthaForm` o `ArthaContainer`, puedes normalizarla con `XHR.defaults.transformResponse`.
565
+
566
+ Importante: configúralo antes de que se registren los componentes. La forma recomendada es hacerlo dentro de `artha:before-register`.
567
+
568
+ ```js
569
+ import { EventBus, XHR } from "./dist/artha.min.js";
570
+
571
+ EventBus.on("artha:before-register", () => {
572
+ XHR.defaults.transformResponse = (xhr) => {
573
+ const raw = xhr.response;
574
+
575
+ // El atributo status se puede omitir si prefieres usar la validación por código HTTP.
576
+ // En este ejemplo se normaliza de forma explícita.
577
+ return {
578
+ status: xhr.status >= 200 && xhr.status < 300 ? "success" : "error",
579
+ message: raw?.message ?? null,
580
+ errors: raw?.errors ?? null,
581
+ data: raw?.result ?? raw?.data ?? raw
582
+ };
583
+ };
584
+ });
585
+ ```
586
+
587
+ Ejemplo de respuesta original:
588
+
589
+ ```json
590
+ {
591
+ "result": [
592
+ { "id": 1, "name": "Ana" },
593
+ { "id": 2, "name": "Luis" }
594
+ ],
595
+ "message": "OK"
596
+ }
597
+ ```
598
+
599
+ Respuesta normalizada:
600
+
601
+ ```js
602
+ {
603
+ status: "success",
604
+ message: "OK",
605
+ errors: null,
606
+ data: [
607
+ { id: 1, name: "Ana" },
608
+ { id: 2, name: "Luis" }
609
+ ]
610
+ }
611
+ ```
612
+
613
+ ### `TaskQueue`
614
+
615
+ Evita ejecutar dos tareas con el mismo id al mismo tiempo y centraliza el cierre de estados.
616
+
617
+ #### Uso básico
618
+
619
+ ```js
620
+ import { TaskQueue } from "./dist/artha.min.js";
621
+
622
+ const queue = TaskQueue.singleton();
623
+
624
+ queue.loadTask("save-user", "Guardando usuario...", (task) => {
625
+ setTimeout(() => {
626
+ task.resolve({
627
+ status: 200,
628
+ response: JSON.stringify({
629
+ status: "success",
630
+ message: "Usuario guardado",
631
+ data: { id: 1 }
632
+ })
633
+ });
634
+ }, 500);
635
+ }, {
636
+ close: true
637
+ });
638
+ ```
639
+
640
+ #### Defaults
641
+
642
+ ```js
643
+ TaskQueue.defaults = {
644
+ title: "Petición en proceso...",
645
+ close: false,
646
+ message: null
647
+ };
648
+ ```
649
+
650
+ #### Observaciones
651
+
652
+ - cada tarea necesita un id único
653
+ - si se repite el id mientras sigue activa, se cancela la nueva tarea
654
+ - si se pasa un `ArthaMessage`, la cola actualiza sus estados visuales
655
+ - `close: true` intenta cerrar el mensaje automáticamente al finalizar
656
+
657
+ ### `EventBus`
658
+
659
+ Bus global basado en `EventTarget`.
660
+
661
+ #### Uso
662
+
663
+ ```js
664
+ import { EventBus } from "./dist/artha.min.js";
665
+
666
+ const unsubscribe = EventBus.on("users:reload", (data) => {
667
+ console.log("recargar", data);
668
+ });
669
+
670
+ EventBus.emit("users:reload", { source: "manual" });
671
+ EventBus.emitAsync("users:reload", { source: "async" });
672
+ unsubscribe();
673
+ ```
674
+
675
+ #### API pública
676
+
677
+ - `EventBus.emit(name, data)`
678
+ - `EventBus.emitAsync(name, data)`
679
+ - `EventBus.on(name, callback)`
680
+ - `EventBus.once(name, callback)`
681
+ - `EventBus.onAny(callback)`
682
+ - `EventBus.off(name, callback)`
683
+ - `EventBus.clean(name)`
684
+ - `EventBus.clearAll()`
685
+
686
+ #### Debug
687
+
688
+ ```js
689
+ EventBus.debug = true;
690
+ ```
691
+
692
+ ### `Util`
693
+
694
+ Utilidades generales.
695
+
696
+ #### API pública
697
+
698
+ - `Util.getMeta(name)`
699
+ - `Util.getValueByPath(obj, path, defaultValue = null)`
700
+ - `Util.modal(element, visible = -1)`
701
+ - `Util.modalById(id, visible = -1)`
702
+ - `Util.formatMoney(value, options = {})`
703
+ - `Util.numberRandom(min, max)`
704
+ - `Util.withinRange(value, min, max)`
705
+ - `Util.createElement(type, value = null, options = {})`
706
+
707
+ #### Ejemplos
708
+
709
+ ```js
710
+ Util.getMeta("csrf-token");
711
+ Util.getValueByPath({ user: { name: "Ana" } }, "user.name");
712
+ Util.modalById("panel", true);
713
+ Util.formatMoney("1234.5");
714
+ Util.numberRandom(1, 10);
715
+ Util.withinRange(204, 200, 299);
716
+ ```
717
+
718
+ ## Flujo de respuesta esperado
719
+
720
+ `ArthaForm` y `ArthaContainer` funcionan mejor cuando el backend responde con una estructura parecida a esta:
721
+
722
+ ```json
723
+ {
724
+ "status": "success",
725
+ "message": "Operación completada",
726
+ "data": []
727
+ }
728
+ ```
729
+
730
+ También soporta errores con este formato:
731
+
732
+ ```json
733
+ {
734
+ "status": "error",
735
+ "message": "No se pudo completar la operación",
736
+ "errors": {
737
+ "email": ["El correo ya existe"]
738
+ }
739
+ }
740
+ ```
741
+
742
+ Si tu API responde con otro formato, puedes adaptar la salida usando `XHR.defaults.transformResponse`.
743
+
744
+ ## Eventos útiles
745
+
746
+ ### Eventos de componentes
747
+
748
+ - `component-ready`
749
+ - `load`
750
+ - `resolve`
751
+ - `dynamic-content-loaded`
752
+ - `item-rendered`
753
+ - `item-selected`
754
+ - `item-deselected`
755
+ - `search`
756
+ - `cancel-search`
757
+
758
+ ### Eventos globales de Artha
759
+
760
+ - `artha:before-register`
761
+ - `artha:after-register`
762
+
763
+ ### Ejemplo de refresco entre componentes
764
+
765
+ ```js
766
+ import { EventBus } from "./dist/artha.min.js";
767
+
768
+ EventBus.emit("users:updated", { id: 3, name: "Nuevo nombre" });
769
+ EventBus.emit("users:reload");
770
+ ```
771
+
772
+ Y en el contenedor:
773
+
774
+ ```html
775
+ <artha-container
776
+ action="/api/users"
777
+ template="user-template"
778
+ refresh-on="users:reload,users:updated">
779
+ </artha-container>
780
+ ```
781
+
782
+ ## Desarrollo
783
+
784
+ ### Instalar dependencias
785
+
786
+ ```bash
787
+ npm install
788
+ ```
789
+
790
+ ### Desarrollo
791
+
792
+ Compilar CSS en modo watch:
793
+
794
+ ```bash
795
+ npm run dev:css
796
+ ```
797
+
798
+ Levantar servidor local:
799
+
800
+ ```bash
801
+ npm run dev:server
802
+ ```
803
+
804
+ Ejecutar ambos:
805
+
806
+ ```bash
807
+ npm run dev
808
+ ```
809
+
810
+ ### Build
811
+
812
+ ```bash
813
+ npm run build
814
+ ```
815
+
816
+ ## Licencia
817
+
818
+ MIT. Consulta [`LICENSE`](./LICENSE).