@luis-angel-martin-dzul/input-styles 0.0.34 → 0.0.38

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,1361 +1,1420 @@
1
- # 📦 Input Style
2
-
3
- **Librería de componentes y formularios Vue 3**
4
-
5
- Incluye componentes de inputs, selects, botones, paginación y más, estilizados con TailwindCSS y listos para producción.
6
-
7
- ------
8
-
9
- ![Última actualización](https://img.shields.io/badge/Actualizado-16%20de%20Septiembre%20del%2026-blue)
10
- ![Versión](https://img.shields.io/badge/versión-0.0.29-indigo)
11
-
12
- ------
13
-
14
- # 📦 Instalación
15
-
16
- La librería se distribuye mediante **npm**:
17
-
18
- ```
19
- npm install @luis-angel-martin-dzul/input-styles
20
- ```
21
-
22
- ------
23
-
24
- # 🚀 Uso en Vue + Vite
25
-
26
- En `main.js` o `main.ts`:
27
-
28
- ```js
29
- import { createApp } from 'vue'
30
- import App from './App.vue'
31
-
32
- import '@luis-angel-martin-dzul/input-styles/style.css'
33
- import InputStyle from '@luis-angel-martin-dzul/input-styles'
34
-
35
- const app = createApp(App)
36
-
37
- app.use(InputStyle)
38
- app.mount('#app')
39
- ```
40
-
41
- Al instalar el plugin se registran automáticamente todos los componentes y las siguientes **propiedades globales** (disponibles en cualquier componente con la Options API):
42
-
43
- | Propiedad global | Equivale a | Descripción |
44
- | ---------------- | ---------- | ----------- |
45
- | `$alertstyle` | `AlertStyle` | Notificaciones tipo toast (ver [AlertStyle](#alertstyle-notificaciones-tipo-toast)). |
46
- | `$dialogstyle` | `DialogStyle` | Diálogos de confirmación / info / carga (ver [DialogStyle](#dialogstyle-diálogos-modales)). |
47
- | `$utilstyle` | `UtilStyle` | Utilidades internas (ej: generación de UUID). |
48
-
49
- ------
50
-
51
- # Estilos default
52
- Para cargar correctamente los estilos debes realizar algo como lo siguiente:
53
-
54
- Agrega el @source para indicar que el componente requiere de tailwindcss
55
-
56
- ```css
57
- @import "tailwindcss";
58
-
59
- @source "../node_modules/@luis-angel-martin-dzul/input-styles/**/*"
60
- ```
61
-
62
- # 🎨 Estilos personalizados (Style Registry)
63
-
64
- La librería expone un sistema de **tokens de estilo configurables** que permite cambiar la apariencia de todos los componentes desde un solo lugar, sin necesidad de modificar el código fuente.
65
-
66
- Los estilos se configuran **antes de montar la app**, importando `configureStyles` directamente.
67
-
68
- ## Opción A — vía `app.use` (recomendado)
69
-
70
- ```js
71
- import VueInputYuc from '@luis-angel-martin-dzul/input-styles'
72
-
73
- app.use(VueInputYuc, {
74
- styles: {
75
- INPUT_BASE: 'w-full px-0 py-2 bg-transparent border-b focus:outline-none ...',
76
- ICON_VALID: 'text-emerald-500',
77
- // solo los tok ens que quieras cambiar
78
- }
79
- })
80
- ```
81
-
82
- ## Opción B — importación directa
83
-
84
- ```js
85
- import { configureStyles } from '@luis-angel-martin-dzul/input-styles'
86
-
87
- configureStyles({
88
- LOADER_ARC_STROKE: '#3B82F6',
89
- REQUIRED_ASTERISK: 'text-red-500',
90
- })
91
- ```
92
-
93
- ⚠️ `configureStyles()` debe llamarse **antes** de que los componentes se monten. Los cambios no son reactivos después del montaje inicial.
94
-
95
- ------
96
-
97
- ## 📋 Referencia completa de tokens
98
-
99
- ### Tokens auxiliares de label
100
- _Afecta: todos los componentes que tienen `label` o `description`_
101
-
102
- Estos tokens controlan partes individuales del label y del texto descriptivo. Al sobrescribir uno, las funciones `inputLabelClass`, `inputCheckLabelClass` e `inputDescriptionClass` lo recogen automáticamente **sin necesidad de sobreescribir la función entera**.
103
-
104
- | Token | Tipo | Default | Descripción |
105
- | ----- | ---- | ------- | ----------- |
106
- | `INPUT_BASE_TITLE` | `string` | `'block text-sm font-medium'` | Clases estructurales del `<label>` de inputs de texto. |
107
- | `INPUT_CHECK_BASE_TITLE` | `string` | `'block text-sm font-medium'` | Clases estructurales del `<label>` de checkbox, radio y switch. |
108
- | `LABEL_DISABLED_STATIC` | `string` | `'text-gray-400'` | Color del label y descripción cuando el campo está deshabilitado. |
109
- | `LABEL_ENABLED_STATIC` | `string` | `'text-gray-700'` | Color del label cuando el campo está habilitado. |
110
-
111
- ```js
112
- configureStyles({
113
- LABEL_DISABLED_STATIC: 'text-gray-400',
114
- LABEL_ENABLED_STATIC: 'text-gray-900',
115
- INPUT_BASE_TITLE: 'block font-semibold mb-1 text-sm',
116
- })
117
- ```
118
-
119
- ---
120
-
121
- ### Inputs de texto
122
- _Afecta: `InputTextStyle`, `InputNumberStyle`, `InputPasswordStyle`, `TextAreaStyle`, `InputDateStyle`, `InputDateTimeStyle`, `InputTimeStyle`, `SelectStyle`, `InputAutocompleteStyle`_
123
-
124
- | Token | Tipo | Default | Descripción |
125
- | ----- | ---- | ------- | ----------- |
126
- | `INPUT_BASE` | `string` | `'w-full text-gray-800 bg-white px-3 py-1.5 rounded border ...'` | Clases base del `<input>`. No incluye borde ni ring (eso lo maneja `inputValidateClass`). |
127
- | `inputValidateClass` | `function(validate)` | Ver abajo | Retorna las clases de borde y ring según el estado de validación. `validate`: `true` / `false` / `null`. |
128
- | `inputLabelClass` | `function(disabled)` | Ver abajo | Retorna las clases del `<label>`. Internamente usa `INPUT_BASE_TITLE`, `LABEL_DISABLED_STATIC` y `LABEL_ENABLED_STATIC`. |
129
- | `inputDescriptionClass` | `function(validate, alignRight?)` | Ver abajo | Retorna las clases del texto de descripción. Internamente usa `ICON_VALID`, `ICON_INVALID` y `LABEL_DISABLED_STATIC`. |
130
-
131
- > 💡 Si solo quieres cambiar un color de validación, basta con sobrescribir `ICON_INVALID` o `ICON_VALID` — `inputDescriptionClass` los recoge automáticamente.
132
-
133
- ```js
134
- configureStyles({
135
- ICON_INVALID: 'text-red-500',
136
- ICON_VALID: 'text-emerald-500',
137
- })
138
-
139
- // Override completo de las funciones (cuando necesitas control total)
140
- configureStyles({
141
- inputValidateClass: (validate) => {
142
- if (validate === false) return 'border-red-500 focus:ring-red-200'
143
- if (validate === true) return 'border-emerald-500 focus:ring-emerald-200'
144
- return 'border-gray-200 focus:ring-indigo-200'
145
- },
146
- inputLabelClass: (disabled) => ['block font-bold mb-2', disabled ? 'text-gray-400' : 'text-gray-900'],
147
- inputDescriptionClass: (validate, alignRight = true) => [
148
- 'block w-full text-sm',
149
- alignRight && 'text-right',
150
- validate === false ? 'text-red-500' : validate === true ? 'text-emerald-500' : 'text-gray-500',
151
- ],
152
- })
153
- ```
154
-
155
- ---
156
-
157
- ### Select
158
- _Afecta: `SelectStyle`, `InputAutocompleteStyle`_
159
-
160
- | Token | Tipo | Default | Descripción |
161
- | ----- | ---- | ------- | ----------- |
162
- | `SELECT_DISABLED` | `string` | `'w-full px-3 py-1.5 rounded bg-gray-100 ...'` | Clases del input en estado disabled. |
163
- | `SELECT_DROPDOWN` | `string` | `'absolute z-20 w-full bg-white border ...'` | Clases del contenedor `<ul>` del dropdown. |
164
- | `SELECT_OPTION_BASE` | `string` | `'px-3 py-1 cursor-pointer text-md'` | Clases base de cada `<li>` del dropdown. |
165
- | `selectOptionClass` | `function(isSelected, isHighlighted)` | Ver abajo | Retorna clases dinámicas por opción según su estado. |
166
- | `SELECT_OPTGROUP` | `string` | `'px-2 pt-1 pb-0.5 text-xs font-semibold uppercase ...'` | Clases del encabezado de un grupo de opciones (`optgroup`). |
167
- | `SELECT_EMPTY` | `string` | `'px-3 py-1 text-gray-400 cursor-not-allowed'` | Clases del mensaje "Sin registros". |
168
-
169
- ```js
170
- configureStyles({
171
- selectOptionClass: (isSelected, isHighlighted) => {
172
- if (isSelected) return 'bg-indigo-100 text-indigo-900 font-medium'
173
- if (isHighlighted) return 'bg-indigo-50 text-gray-900'
174
- return 'hover:bg-indigo-50 text-gray-800'
175
- },
176
- })
177
- ```
178
-
179
- ---
180
-
181
- ### Checkbox, Radio y Switch
182
- _Afecta: `InputCheckStyle`, `InputRadioStyle`, `InputSwitchStyle`_
183
-
184
- | Token | Tipo | Default | Descripción |
185
- | ----- | ---- | ------- | ----------- |
186
- | `inputCheckLabelClass` | `function(disabled)` | Ver abajo | Clases del label de checks. |
187
- | `CHECK_BOX_BASE` | `string` | `'w-5 h-5 flex items-center justify-center rounded border-2 transition'` | Estructura base del cuadro del checkbox. |
188
- | `RADIO_BASE` | `string` | `'w-5 h-5 flex items-center justify-center rounded-full transition'` | Estructura base del círculo del radio. |
189
- | `SWITCH_BASE` | `string` | `'w-10 h-5 relative flex items-center rounded-full transition border-2'` | Estructura base de la pista del switch. |
190
- | `checkBoxClass` | `function(model, disabled)` | Ver abajo | Clases de color del checkbox según estado activo/disabled. |
191
- | `radioClass` | `function(model, disabled)` | Ver abajo | Clases de color del radio button según estado. |
192
- | `switchClass` | `function(model, disabled)` | Ver abajo | Clases de color de la pista del switch. |
193
- | `switchDotClass` | `function(model, disabled)` | Ver abajo | Clases de color de la bola interior del switch. |
194
-
195
- ```js
196
- configureStyles({
197
- checkBoxClass: (model, disabled) => {
198
- if (disabled) return model
199
- ? 'bg-indigo-200 border-indigo-200 cursor-not-allowed'
200
- : 'bg-gray-100 border-gray-200 cursor-not-allowed'
201
- return model
202
- ? 'bg-indigo-500 border-indigo-500 peer-focus:ring-2 peer-focus:ring-indigo-200'
203
- : 'bg-white border-gray-300 peer-focus:ring-2 peer-focus:ring-indigo-200'
204
- },
205
- })
206
- ```
207
-
208
- ---
209
-
210
- ### Paginación
211
- _Afecta: `PaginationStyle`_
212
-
213
- | Token | Tipo | Default | Descripción |
214
- | ----- | ---- | ------- | ----------- |
215
- | `PAGINATION_NAV_PREV` | `string` | `'bg-white hover:bg-indigo-50 border ...'` | Clases del botón de página anterior. |
216
- | `PAGINATION_NAV_NEXT` | `string` | `'bg-white hover:bg-indigo-50 border ...'` | Clases del botón de página siguiente. |
217
- | `PAGINATION_PAGE_BASE` | `string` | `'border px-2.5 py-2 cursor-pointer w-8 relative'` | Clases base de cada botón de página. |
218
- | `paginationPageClass` | `function(page, actualPage, index)` | Ver abajo | Retorna clases dinámicas por botón de página. |
219
- | `PAGINATION_SUMMARY` | `string` | `'text-sm text-gray-700'` | Clases del texto de resumen de registros. |
220
-
221
- ---
222
-
223
- ### Loader
224
- _Afecta: `LoaderStyle`_
225
-
226
- | Token | Tipo | Default | Descripción |
227
- | ----- | ---- | ------- | ----------- |
228
- | `LOADER_ARC_STROKE` | `string` | `'#6366F1'` | Color hex del arco animado del loader (indigo-500). |
229
- | `LOADER_MESSAGE` | `string` | `'text-gray-600 text-lg font-medium'` | Clases del mensaje de carga. |
230
-
231
- ---
232
-
233
- ### Iconos y asterisco de requerido
234
- _Afecta: todos los componentes con `required` o iconos de validación_
235
-
236
- | Token | Tipo | Default | Descripción |
237
- | ----- | ---- | ------- | ----------- |
238
- | `REQUIRED_ASTERISK` | `string` | `'text-red-500'` | Clase de color del asterisco `*` en campos requeridos. |
239
- | `ICON_VALID` | `string` | `'text-emerald-500'` | Clase de color del ícono de validación correcta. |
240
- | `ICON_INVALID` | `string` | `'text-red-500'` | Clase de color del ícono de validación errónea. |
241
- | `ICON_STATIC` | `string` | `'text-indigo-400'` | Clase de color de los íconos estáticos (reloj, calendario, ojo, chevron). |
242
- | `ICON_SIZE_STATIC` | `string` | `'w-5 h-5'` | Clases de tamaño de todos los íconos. |
243
-
244
- ---
245
-
246
- ### File Upload y Download
247
- _Afecta: `InputFileUploadStyle`, `InputFileDownloadStyle`_
248
-
249
- | Token | Tipo | Default | Descripción |
250
- | ----- | ---- | ------- | ----------- |
251
- | `FILE_BUTTON_BASE` | `string` | `'flex items-center justify-center gap-3 px-4 py-2 border-2 rounded-xl ...'` | Clases estructurales del botón (compartido entre upload y download). |
252
- | `FILE_BUTTON_ACTIVE` | `string` | `'border-gray-200 bg-white text-gray-700 hover:border-indigo-400 ...'` | Clases del botón en estado normal/activo. |
253
- | `FILE_BUTTON_DISABLED` | `string` | `'border-gray-200 bg-gray-100 text-gray-400'` | Clases del botón en estado disabled. |
254
- | `FILE_ICON_BG` | `string` | `'bg-indigo-300'` | Fondo del círculo de ícono en estado activo. |
255
- | `FILE_ICON_BG_COMPLETE` | `string` | `'bg-emerald-500'` | Fondo del círculo de ícono cuando la descarga está completa. |
256
- | `FILE_ICON_BG_DISABLED` | `string` | `'bg-gray-300'` | Fondo del círculo de ícono en estado disabled. |
257
-
258
- ---
259
-
260
- ### Time Picker
261
- _Afecta: `InputTimeStyle`_
262
-
263
- | Token | Tipo | Default | Descripción |
264
- | ----- | ---- | ------- | ----------- |
265
- | `TIME_PICKER_WRAPPER` | `string` | `'absolute z-20 left-0 top-full rounded-lg bg-white'` | Contenedor flotante del picker. |
266
- | `TIME_PICKER_CONTAINER` | `string` | `'border border-gray-300 rounded-lg p-3'` | Borde interior del picker. |
267
- | `TIME_PICKER_COLUMN` | `string` | `'h-48 overflow-y-auto border border-gray-200 rounded-md ...'` | Columna scrollable de horas/minutos. |
268
- | `TIME_PICKER_AMPM_COLUMN` | `string` | `'h-48 overflow-y-auto border border-gray-200 rounded-md ...'` | Columna scrollable AM/PM. |
269
- | `TIME_PICKER_COLUMN_LABEL` | `string` | `'block text-xs font-medium text-gray-500 mb-1 text-center'` | Label de cada columna ("Hora", "Minuto", "AM/PM"). |
270
- | `TIME_PICKER_ITEM` | `string` | `'px-3 py-1 text-center cursor-pointer hover:bg-indigo-50 ...'` | Fila base de cada ítem del picker. |
271
- | `timePickerItemClass` | `function(isActive)` | Ver abajo | Clases dinámicas de cada ítem según si está seleccionado. |
272
-
273
- ------
274
-
275
- # 🧩 Componentes
276
-
277
- ## Props comunes
278
-
279
- La mayoría de los componentes comparten las siguientes props:
280
-
281
- | Prop | Tipo | Default | Descripción |
282
- | ---- | ---- | ------- | ----------- |
283
- | `modelValue` | `String \| Number \| Boolean` | — | Valor del campo (v-model). |
284
- | `id` | `String` | `null` | Id propio del control. Si se omite, se genera automáticamente (uuid) y se mantiene enlazado con el `<label for>`. |
285
- | `name` | `String` | `null` | Atributo `name` nativo del control (`input` / `textarea` / `file` / `button`). Útil para envío de formularios, autocompletado y accesibilidad. |
286
- | `autofocus` | `Boolean` | `false` | Enfoca el campo automáticamente al montarse. |
287
- | `label` | `String` | `''` | Texto del label del campo. |
288
- | `description` | `String` | `''` | Texto de ayuda debajo del campo. Cambia de color según el estado de validación. |
289
- | `placeholder` | `String` | `''` | Placeholder del input. |
290
- | `disabled` | `Boolean` | `false` | Deshabilita el campo. |
291
- | `required` | `Boolean` | `false` | Marca el campo como requerido (muestra asterisco `*`). |
292
- | `validate` | `Boolean \| null` | `null` | Estado de validación: `true` = válido, `false` = inválido, `null` = sin estado. |
293
- | `mclass` | `String` | `null` | Clases CSS adicionales aplicadas directamente al elemento del input. |
294
-
295
- ## Eventos comunes
296
-
297
- | Evento | Payload | Descripción |
298
- | ------ | ------- | ----------- |
299
- | `update:modelValue` | `value` | Se dispara al cambiar el valor (permite `v-model`). |
300
- | `change` | `value` | Se dispara al cambiar el valor. |
301
- | `blur` | `event` | Se dispara al perder el foco. |
302
-
303
- ------
304
-
305
- ## InputTextStyle
306
-
307
- Input de texto genérico. Soporta tipos `text`, `email`, `search`, `tel`, `url` y `number`.
308
-
309
- ```vue
310
- <InputTextStyle
311
- v-model="nombre"
312
- label="Nombre"
313
- placeholder="Escribe tu nombre"
314
- :required="true"
315
- :validate="validate"
316
- description="Campo requerido"
317
- />
318
- ```
319
-
320
- | Prop | Tipo | Default | Descripción |
321
- | ---- | ---- | ------- | ----------- |
322
- | `modelValue` | `String \| Number` | `''` | Valor del campo. |
323
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
324
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
325
- | `autocomplete` | `String` | `null` | Atributo `autocomplete` nativo (ej: `email`, `username`, `off`). |
326
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
327
- | `readonly` | `Boolean` | `false` | Solo lectura: no editable, pero se envía en el formulario. |
328
- | `label` | `String` | `''` | |
329
- | `description` | `String` | `''` | |
330
- | `placeholder` | `String` | `''` | |
331
- | `type` | `String` | `'text'` | Tipo del input: `text`, `email`, `search`, `tel`, `url`, `number`. |
332
- | `maxlength` | `String` | `null` | Longitud máxima de caracteres. |
333
- | `minlength` | `String` | `null` | Longitud mínima de caracteres. |
334
- | `min` | `String` | `null` | Valor mínimo (solo cuando `type="number"`, aplicado al perder el foco). |
335
- | `max` | `String` | `null` | Valor máximo (solo cuando `type="number"`, aplicado al perder el foco). |
336
- | `disabled` | `Boolean` | `false` | |
337
- | `required` | `Boolean` | `false` | |
338
- | `validate` | `Boolean \| null` | `null` | |
339
- | `mclass` | `String` | `null` | |
340
-
341
- ------
342
-
343
- ## InputNumberStyle
344
-
345
- Input numérico con control preciso de dígitos, decimales, rango y formato de visualización. El valor se emite siempre como string numérico limpio (ej: `"1234.56"`). Al perder el foco se formatea con `toLocaleString`.
346
-
347
- ```vue
348
- <InputNumberStyle
349
- v-model="monto"
350
- label="Monto"
351
- numeric="10,2"
352
- min="0"
353
- max="9999999999"
354
- :required="true"
355
- :validate="validate"
356
- />
357
- ```
358
-
359
- | Prop | Tipo | Default | Descripción |
360
- | ---- | ---- | ------- | ----------- |
361
- | `modelValue` | `String \| Number` | `''` | Valor del campo. |
362
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
363
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
364
- | `autocomplete` | `String` | `null` | Atributo `autocomplete` nativo. |
365
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
366
- | `readonly` | `Boolean` | `false` | Solo lectura: bloquea la edición y los botones ▲/▼, pero se envía en el formulario. |
367
- | `label` | `String` | `''` | |
368
- | `description` | `String` | `''` | |
369
- | `placeholder` | `String` | `''` | |
370
- | `numeric` | `String` | `null` | Formato tipo SQL: `"10,2"` → 10 dígitos enteros, 2 decimales. Si se omite, usa `10,2` por defecto. |
371
- | `min` | `String` | `null` | Valor mínimo (aplicado al perder el foco). Si es `null` o negativo, permite negativos. |
372
- | `max` | `String` | `null` | Valor máximo (aplicado al perder el foco). |
373
- | `alignLeft` | `Boolean` | `false` | Posición de los botones de paso (▲/▼) respecto al valor. `false` = botones a la izquierda, valor a la derecha (`Rows \| Input`); `true` = botones a la derecha, valor a la izquierda (`Input \| Rows`). |
374
- | `disabled` | `Boolean` | `false` | |
375
- | `required` | `Boolean` | `false` | |
376
- | `validate` | `Boolean \| null` | `null` | |
377
- | `mclass` | `String` | `null` | |
378
-
379
- **Comportamiento:**
380
- - Incluye botones **caret ▲ / ▼** para aumentar/disminuir el valor en unidades (respetando `min`/`max`). Por defecto se alinean a la izquierda del campo (con el valor a la derecha); con `alignLeft` se mueven a la derecha y el valor pasa a la izquierda. No tienen separador y usan el color global de los iconos (`ICON_STATIC`).
381
- - Si **mantienes presionado** un botón, tras una breve pausa el valor cambia de forma **continua** hasta soltar.
382
- - Puedes escribir `0` y luego `.` para empezar los decimales: el punto se conserva a la espera de los dígitos decimales (solo si los decimales están activados en `numeric`).
383
- - Al **enfocar** el campo, si el valor es `0` (o está vacío) se limpia, para que al escribir reemplaces el valor en lugar de anteponerlo.
384
- - Al **perder el foco**, si el campo queda vacío o en `0`, se conserva el valor `0` formateado (ej: `"0.00"`), aplicando además los límites `min`/`max`.
385
-
386
- **Navegación por teclado:**
387
- - `↑` / `↓` — aumentar/disminuir el valor en unidades, respetando `min`/`max`.
388
-
389
- ------
390
-
391
- ## InputPasswordStyle
392
-
393
- Input de contraseña con toggle para mostrar/ocultar el texto.
394
-
395
- ```vue
396
- <InputPasswordStyle
397
- v-model="password"
398
- label="Contraseña"
399
- placeholder="Mínimo 8 caracteres"
400
- :required="true"
401
- :validate="validate"
402
- />
403
- ```
404
-
405
- | Prop | Tipo | Default | Descripción |
406
- | ---- | ---- | ------- | ----------- |
407
- | `modelValue` | `String \| Number` | `''` | Valor del campo. |
408
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
409
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
410
- | `autocomplete` | `String` | `null` | Atributo `autocomplete` nativo (ej: `current-password`, `new-password`). |
411
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
412
- | `readonly` | `Boolean` | `false` | Solo lectura: no editable, pero se envía en el formulario. |
413
- | `label` | `String` | `''` | |
414
- | `description` | `String` | `''` | |
415
- | `placeholder` | `String` | `''` | |
416
- | `maxlength` | `String` | `null` | Longitud máxima. |
417
- | `disabled` | `Boolean` | `false` | |
418
- | `required` | `Boolean` | `false` | |
419
- | `validate` | `Boolean \| null` | `null` | |
420
- | `mclass` | `String` | `null` | |
421
-
422
- ------
423
-
424
- ## TextAreaStyle
425
-
426
- Área de texto multi-línea.
427
-
428
- ```vue
429
- <TextAreaStyle
430
- v-model="comentario"
431
- label="Comentario"
432
- placeholder="Escribe aquí..."
433
- row="5"
434
- :maxlength="500"
435
- :validate="validate"
436
- />
437
- ```
438
-
439
- | Prop | Tipo | Default | Descripción |
440
- | ---- | ---- | ------- | ----------- |
441
- | `modelValue` | `String` | `''` | Valor del campo. |
442
- | `name` | `String` | `null` | Atributo `name` nativo del `<textarea>`. |
443
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
444
- | `autocomplete` | `String` | `null` | Atributo `autocomplete` nativo. |
445
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
446
- | `readonly` | `Boolean` | `false` | Solo lectura: no editable, pero se envía en el formulario. |
447
- | `label` | `String` | `''` | |
448
- | `description` | `String` | `''` | |
449
- | `placeholder` | `String` | `''` | |
450
- | `row` | `String` | `'3'` | Número de filas visibles del textarea. |
451
- | `maxlength` | `String` | `null` | Longitud máxima de caracteres. |
452
- | `disabled` | `Boolean` | `false` | |
453
- | `required` | `Boolean` | `false` | |
454
- | `validate` | `Boolean \| null` | `null` | |
455
- | `mclass` | `String` | `null` | |
456
-
457
- ------
458
-
459
- ## SelectStyle
460
-
461
- Select personalizado con dropdown, navegación por teclado y búsqueda por tipeo. El dropdown se posiciona automáticamente arriba o abajo según el espacio disponible.
462
-
463
- ```vue
464
- <SelectStyle
465
- v-model="pais"
466
- label="País"
467
- :list="paises"
468
- return-value="value"
469
- :required="true"
470
- :validate="validate"
471
- />
472
- ```
473
-
474
- ```js
475
- // Formato del array `list` — lista plana
476
- const paises = [
477
- { value: 1, text: 'México' },
478
- { value: 2, text: 'España' },
479
- { value: 3, text: 'Argentina' },
480
- ]
481
-
482
- // El `value` es opcional: si se omite, se usa el `text` como valor
483
- const colores = [
484
- { text: 'Rojo' },
485
- { text: 'Verde' },
486
- ]
487
-
488
- // Lista agrupada — cada grupo tiene un `label` y sus `options`
489
- const ubicaciones = [
490
- { label: 'América', options: [{ value: 1, text: 'México' }, { value: 2, text: 'Argentina' }] },
491
- { label: 'Europa', options: [{ value: 3, text: 'España' }, { value: 4, text: 'Francia' }] },
492
- ]
493
- ```
494
-
495
- | Prop | Tipo | Default | Descripción |
496
- | ---- | ---- | ------- | ----------- |
497
- | `modelValue` | `String \| Number \| Object` | — | Valor seleccionado. |
498
- | `list` | `Array` | `[]` | Array de opciones. Acepta lista plana `{ value, text }` o agrupada `{ label, options: [...] }`. El `value` es opcional: si se omite, se usa el `text` como valor. |
499
- | `returnValue` | `String` | `'value'` | Qué se emite al seleccionar: `'value'` (item.value), `'text'` (item.text) u `'object'` (el item completo). |
500
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
501
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
502
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
503
- | `label` | `String` | `''` | |
504
- | `description` | `String` | `''` | |
505
- | `wrapOptions` | `Boolean` | `false` | `true` = permite que el texto de cada opción haga salto de línea; `false` = lo trunca con elipsis. |
506
- | `disabled` | `Boolean` | `false` | |
507
- | `required` | `Boolean` | `false` | |
508
- | `validate` | `Boolean \| null` | `null` | |
509
- | `mclass` | `String` | `null` | |
510
-
511
- **Navegación por teclado:**
512
- - `↑` / `↓` — navegar opciones
513
- - `Enter` — seleccionar la opción resaltada
514
- - `Escape` — cerrar el dropdown
515
- - Tipear letras — búsqueda inmediata en la lista
516
-
517
- ------
518
-
519
- ## InputAutocompleteStyle
520
-
521
- Autocomplete basado en `SelectStyle`, pero con el **input editable**: el usuario escribe para filtrar las opciones en vivo. Mantiene el mismo formato de `list` (plana o agrupada) y la misma navegación por teclado y posicionamiento automático del dropdown.
522
-
523
- Incluye un estado de **carga/búsqueda** controlado por la prop `loading`: cuando es `true`, oculta las opciones y muestra el mensaje _"Buscando coincidencias..."_ — pensado para búsquedas asíncronas donde el listado se resuelve en el servidor.
524
-
525
- ```vue
526
- <InputAutocompleteStyle
527
- v-model="pais"
528
- label="País"
529
- :list="paises"
530
- placeholder="Escribe para buscar"
531
- :loading="buscando"
532
- return-value="value"
533
- :validate="validate"
534
- @search="onSearch"
535
- />
536
- ```
537
-
538
- ```js
539
- // El evento @search se emite en cada tecleo; úsalo para disparar una
540
- // búsqueda asíncrona y controlar la prop `loading`.
541
- function onSearch(query) {
542
- buscando.value = true
543
- fetchPaises(query).then(res => {
544
- paises.value = res
545
- buscando.value = false
546
- })
547
- }
548
- ```
549
-
550
- ```js
551
- // Formato del array `list` — idéntico a SelectStyle (plana o agrupada)
552
- const paises = [
553
- { value: 1, text: 'México' },
554
- { value: 2, text: 'España' },
555
- { value: 3, text: 'Argentina' },
556
- ]
557
- ```
558
-
559
- | Prop | Tipo | Default | Descripción |
560
- | ---- | ---- | ------- | ----------- |
561
- | `modelValue` | `String \| Number \| Object` | — | Valor seleccionado. |
562
- | `list` | `Array` | `[]` | Array de opciones. Acepta lista plana `{ value, text }` o agrupada `{ label, options: [...] }`. El `value` es opcional: si se omite, se usa el `text` como valor. |
563
- | `returnValue` | `String` | `'value'` | Qué se emite al seleccionar: `'value'` (item.value), `'text'` (item.text) u `'object'` (el item completo). |
564
- | `loading` | `Boolean` | `false` | Cuando es `true`, oculta las opciones y muestra "Buscando coincidencias...". Úsalo mientras resuelves una búsqueda asíncrona. |
565
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
566
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
567
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
568
- | `label` | `String` | `''` | |
569
- | `description` | `String` | `''` | |
570
- | `placeholder` | `String` | `''` | |
571
- | `wrapOptions` | `Boolean` | `false` | `true` = permite que el texto de cada opción haga salto de línea; `false` = lo trunca con elipsis. |
572
- | `disabled` | `Boolean` | `false` | |
573
- | `required` | `Boolean` | `false` | |
574
- | `validate` | `Boolean \| null` | `null` | |
575
- | `mclass` | `String` | `null` | |
576
-
577
- **Eventos:**
578
- - `search` — se emite en cada cambio del texto escrito, con el `query` actual como payload. Ideal para búsquedas asíncronas.
579
- - `update:modelValue` / `change` — emiten el valor al seleccionar una opción (según `returnValue`).
580
- - `blur` — al cerrarse el dropdown.
581
-
582
- **Comportamiento:**
583
- - Al abrir (foco o clic en el chevron) muestra todas las opciones; al teclear, filtra por coincidencia de texto (sin distinguir mayúsculas).
584
- - Al seleccionar una opción, el input muestra su `text`.
585
- - Si se cierra el dropdown sin seleccionar, el input vuelve a reflejar la selección real (descarta el texto escrito sin confirmar).
586
-
587
- **Navegación por teclado:**
588
- - `↑` / `↓` — navegar opciones
589
- - `Enter` — seleccionar la opción resaltada
590
- - `Escape` — cerrar el dropdown
591
-
592
- ------
593
-
594
- ## InputCheckStyle
595
-
596
- Checkbox estilizado con label y descripción.
597
-
598
- ```vue
599
- <InputCheckStyle
600
- v-model="aceptaTerminos"
601
- label="Acepto los términos y condiciones"
602
- :required="true"
603
- />
604
- ```
605
-
606
- | Prop | Tipo | Default | Descripción |
607
- | ---- | ---- | ------- | ----------- |
608
- | `modelValue` | `Boolean \| Array \| String \| Number` | `false` | Estado del check (modo simple) o array de valores seleccionados (modo grupo). |
609
- | `value` | `String \| Number \| Boolean` | `null` | Valor que aporta este checkbox al grupo. Se usa cuando `modelValue` es un `Array` (checkbox múltiple nativo). |
610
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. Comparte el mismo `name` entre las casillas del grupo. |
611
- | `id` | `String` | `null` | Id propio del `<input>`; si se omite se genera automáticamente. |
612
- | `label` | `String` | `''` | |
613
- | `description` | `String` | `''` | |
614
- | `disabled` | `Boolean` | `false` | |
615
- | `required` | `Boolean` | `false` | |
616
- | `validate` | `Boolean \| null` | `null` | |
617
- | `mclass` | `String` | `null` | Clases adicionales del cuadro del checkbox. |
618
-
619
- **Dos modos de uso:**
620
-
621
- - **Modo simple (por defecto):** con `modelValue` booleano, se comporta como un check `true`/`false`.
622
-
623
- ```vue
624
- <InputCheckStyle v-model="acepto" label="Acepto los términos" />
625
- ```
626
-
627
- - **Modo grupo nativo:** enlaza varias casillas a un `v-model` de tipo **array**, cada una con su `value` y el mismo `name`. Marcar/desmarcar agrega o quita el `value` del array, y en un `<form>` nativo el navegador envía un `name=<value>` por cada casilla marcada (`FormData.getAll`).
628
-
629
- ```vue
630
- <form @submit.prevent="onSubmit">
631
- <InputCheckStyle v-model="perms" name="perm" value="read" label="Leer" />
632
- <InputCheckStyle v-model="perms" name="perm" value="write" label="Escribir" />
633
- <InputCheckStyle v-model="perms" name="perm" value="delete" label="Eliminar" />
634
- </form>
635
- ```
636
-
637
- ```js
638
- // perms === ['read', 'write'] → FormData del <form>: perm=read, perm=write
639
- data() { return { perms: ['read', 'write'] } },
640
- onSubmit(e) { console.log(new FormData(e.target).getAll('perm')) }
641
- ```
642
-
643
- **Eventos:** `update:modelValue` y `change` emiten el array actualizado (modo grupo) o el booleano (modo simple).
644
-
645
- ------
646
-
647
- ## InputRadioStyle
648
-
649
- Radio button estilizado con label y descripción.
650
-
651
- ```vue
652
- <InputRadioStyle
653
- v-model="opcionA"
654
- label="Opción A"
655
- />
656
- ```
657
-
658
- | Prop | Tipo | Default | Descripción |
659
- | ---- | ---- | ------- | ----------- |
660
- | `modelValue` | `String \| Number \| Boolean` | `false` | Valor seleccionado del grupo (modo grupo) o estado del toggle (modo booleano). |
661
- | `value` | `String \| Number \| Boolean` | `null` | Valor que representa este radio dentro del grupo. Si se define, actúa como radio nativo real (`type="radio"`); si se omite, se comporta como toggle booleano. |
662
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. **Requerido** para agrupar varios radios en modo nativo. |
663
- | `id` | `String` | `null` | Id propio del `<input>`; si se omite se genera automáticamente. |
664
- | `label` | `String` | `''` | |
665
- | `description` | `String` | `''` | |
666
- | `disabled` | `Boolean` | `false` | |
667
- | `required` | `Boolean` | `false` | |
668
- | `validate` | `Boolean \| null` | `null` | |
669
- | `mclass` | `String` | `null` | Clases adicionales del círculo del radio. |
670
-
671
- **Dos modos de uso:**
672
-
673
- - **Modo booleano (por defecto):** sin `value`, se comporta como un toggle `true`/`false`. Útil para una sola opción.
674
-
675
- ```vue
676
- <InputRadioStyle v-model="acepto" label="Acepto" />
677
- ```
678
-
679
- - **Modo grupo nativo:** al definir `value` en cada radio y compartir el mismo `name`, se comportan como un grupo de radios reales (`type="radio"`): seleccionar uno deselecciona los demás, `v-model` guarda el `value` elegido y en un `<form>` nativo el navegador envía `name=<value seleccionado>`.
680
-
681
- ```vue
682
- <form @submit.prevent="onSubmit">
683
- <InputRadioStyle v-model="plan" name="plan" value="free" label="Free" />
684
- <InputRadioStyle v-model="plan" name="plan" value="pro" label="Pro" />
685
- <InputRadioStyle v-model="plan" name="plan" value="enterprise" label="Enterprise" />
686
- </form>
687
- ```
688
-
689
- ```js
690
- // plan === 'pro' → FormData del <form> contiene plan=pro
691
- onSubmit(e) { console.log(new FormData(e.target).get('plan')) }
692
- ```
693
-
694
- **Eventos:** `update:modelValue` y `change` emiten el `value` seleccionado (modo grupo) o el booleano (modo toggle).
695
-
696
- ------
697
-
698
- ## InputSwitchStyle
699
-
700
- Toggle switch estilizado.
701
-
702
- ```vue
703
- <InputSwitchStyle
704
- v-model="activo"
705
- label="Activar notificaciones"
706
- />
707
- ```
708
-
709
- | Prop | Tipo | Default | Descripción |
710
- | ---- | ---- | ------- | ----------- |
711
- | `modelValue` | `Boolean` | `false` | Estado del switch. |
712
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
713
- | `id` | `String` | `null` | Id propio del `<input>`; si se omite se genera automáticamente. |
714
- | `label` | `String` | `''` | |
715
- | `description` | `String` | `''` | |
716
- | `disabled` | `Boolean` | `false` | |
717
- | `required` | `Boolean` | `false` | |
718
- | `validate` | `Boolean \| null` | `null` | |
719
- | `mclass` | `String` | `null` | Clases adicionales de la pista del switch. |
720
-
721
- ------
722
-
723
- ## InputDateStyle
724
-
725
- Selector de fecha con máscara `dd/mm/aaaa`, navegación por teclado entre secciones y calendario visual nativo (sin dependencias externas). El valor se emite en formato `YYYY-MM-DD`.
726
-
727
- El calendario incluye vistas de días, meses y años, y un botón **Hoy** para navegar rápidamente a la fecha actual.
728
-
729
- ```vue
730
- <InputDateStyle
731
- v-model="fechaNacimiento"
732
- label="Fecha de nacimiento"
733
- min="1900-01-01"
734
- max="2100-12-31"
735
- :validate="validate"
736
- />
737
- ```
738
-
739
- | Prop | Tipo | Default | Descripción |
740
- | ---- | ---- | ------- | ----------- |
741
- | `modelValue` | `String \| Date` | — | Valor de la fecha en formato `YYYY-MM-DD` o `Date`. |
742
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
743
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
744
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
745
- | `label` | `String` | `''` | |
746
- | `description` | `String` | `''` | |
747
- | `min` | `String` | `'1900-01-01'` | Fecha mínima seleccionable en el calendario (formato `YYYY-MM-DD`). |
748
- | `max` | `String` | `'2100-12-31'` | Fecha máxima seleccionable en el calendario (formato `YYYY-MM-DD`). |
749
- | `disabledDates` | `Array` | `[]` | Fechas deshabilitadas. Array de strings `'YYYY-MM-DD'` o objetos `Date`. |
750
- | `disabled` | `Boolean` | `false` | |
751
- | `readonly` | `Boolean` | `false` | Solo lectura: muestra el valor pero bloquea la edición por teclado. |
752
- | `required` | `Boolean` | `false` | |
753
- | `validate` | `Boolean \| null` | `null` | |
754
- | `mclass` | `String` | `null` | |
755
-
756
- **Valor emitido:** `'YYYY-MM-DD'` (string) o `''` si la fecha es inválida o está vacía.
757
-
758
- **Navegación por teclado:**
759
- - Escribe los dígitos y el foco avanza automáticamente entre día → mes → año
760
- - `↑` / `↓` — incrementar/decrementar la sección activa (la fecha resultante se ajusta a los límites `min`/`max`)
761
- - `←` / `→` — mover entre secciones
762
- - `Tab` / `Shift + Tab` — mover entre secciones (al llegar al extremo, el foco sale del campo)
763
- - `Backspace` / `Delete` — limpiar la sección activa
764
- - Clic en el ícono 📅 — abre/cierra el calendario visual
765
-
766
- ------
767
-
768
- ## InputTimeStyle
769
-
770
- Selector de hora con picker de columnas scrollables. Soporta formato 12h (AM/PM) o 24h. El valor se emite en formato `HH:mm`.
771
-
772
- ```vue
773
- <InputTimeStyle
774
- v-model="horaInicio"
775
- label="Hora de inicio"
776
- min="08:00"
777
- max="18:00"
778
- :is24h="true"
779
- :validate="validate"
780
- />
781
- ```
782
-
783
- | Prop | Tipo | Default | Descripción |
784
- | ---- | ---- | ------- | ----------- |
785
- | `modelValue` | `String` | — | Valor de la hora en formato `HH:mm`. |
786
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
787
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
788
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
789
- | `label` | `String` | `''` | |
790
- | `description` | `String` | `''` | |
791
- | `min` | `String` | `null` | Hora mínima (ej: `'08:00'`). |
792
- | `max` | `String` | `null` | Hora máxima (ej: `'18:00'`). |
793
- | `is24h` | `Boolean` | `false` | `true` = formato 24h, `false` = formato 12h con selector AM/PM. |
794
- | `disabled` | `Boolean` | `false` | |
795
- | `required` | `Boolean` | `false` | |
796
- | `validate` | `Boolean \| null` | `null` | |
797
- | `mclass` | `String` | `null` | |
798
-
799
- **Valor emitido:** `'HH:mm'` en formato 24h siempre, independientemente del modo de visualización.
800
-
801
- ------
802
-
803
- ## InputDateTimeStyle
804
-
805
- Selector combinado de fecha y hora con máscara `dd/mm/aaaa hh:mm` (o `dd/mm/aaaa hh:mm a.m.` en modo 12h), calendario visual nativo y picker de columnas scrollables para la hora. El valor se emite en formato `YYYY-MM-DDTHH:mm:00.000`.
806
-
807
- ```vue
808
- <InputDateTimeStyle
809
- v-model="fechaHora"
810
- label="Fecha y hora"
811
- min="2000-01-01"
812
- max="2100-12-31"
813
- :is24h="true"
814
- :validate="validate"
815
- />
816
- ```
817
-
818
- | Prop | Tipo | Default | Descripción |
819
- | ---- | ---- | ------- | ----------- |
820
- | `modelValue` | `String \| Date` | — | Valor en formato `YYYY-MM-DDTHH:mm:00.000` o `Date`. |
821
- | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
822
- | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
823
- | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
824
- | `label` | `String` | `''` | |
825
- | `description` | `String` | `''` | |
826
- | `min` | `String` | `'1900-01-01'` | Fecha mínima seleccionable (formato `YYYY-MM-DD`). |
827
- | `max` | `String` | `'2100-12-31'` | Fecha máxima seleccionable (formato `YYYY-MM-DD`). |
828
- | `disabledDates` | `Array` | `[]` | Fechas deshabilitadas. Array de strings `'YYYY-MM-DD'` o `Date`. |
829
- | `is24h` | `Boolean` | `false` | `true` = formato 24h, `false` = formato 12h con selector a.m./p.m. |
830
- | `disabled` | `Boolean` | `false` | |
831
- | `readonly` | `Boolean` | `false` | Solo lectura. |
832
- | `required` | `Boolean` | `false` | |
833
- | `validate` | `Boolean \| null` | `null` | |
834
- | `mclass` | `String` | `null` | |
835
-
836
- **Valor emitido:** `'YYYY-MM-DDTHH:mm:00.000'` (string) o `''` si la fecha/hora es inválida o está vacía.
837
-
838
- **Comportamiento del picker:**
839
- - El calendario permanece abierto al seleccionar la fecha, para que puedas ajustar también la hora.
840
- - El selector de hora embebido en el picker usa las mismas columnas scrollables que `InputTimeStyle`.
841
- - Botón **Ahora** — establece la fecha y hora actuales de un solo clic.
842
-
843
- **Navegación por teclado:**
844
- - Escribe los dígitos y el foco avanza automáticamente: día → mes → año → hora → minuto → a.m./p.m.
845
- - `↑` / `↓` — incrementar/decrementar la sección activa (la fecha se ajusta a los límites `min`/`max`)
846
- - `←` / `→` — mover entre secciones
847
- - `Tab` / `Shift + Tab` — mover entre secciones (al llegar al extremo, el foco sale del campo)
848
- - `Backspace` / `Delete` — limpiar la sección activa
849
- - `a` / `p` — cambiar entre a.m. y p.m. (solo modo 12h)
850
- - Clic en el ícono 📅 — abre/cierra el picker
851
-
852
- ------
853
-
854
- ## InputFileUploadStyle
855
-
856
- Botón para adjuntar archivos con validación de tamaño y formato.
857
-
858
- ```vue
859
- <InputFileUploadStyle
860
- v-model="archivo"
861
- :formats="['pdf', 'docx']"
862
- max-size-m-b="10"
863
- :state="uploadState"
864
- />
865
- ```
866
-
867
- | Prop | Tipo | Default | Descripción |
868
- | ---- | ---- | ------- | ----------- |
869
- | `modelValue` | `File` | `null` | Archivo seleccionado. |
870
- | `name` | `String` | `null` | Atributo `name` nativo del `<input type="file">`. |
871
- | `id` | `String` | `null` | Id propio del `<input type="file">`. |
872
- | `disabled` | `Boolean` | `false` | Deshabilita el botón. |
873
- | `formats` | `Array` | `[]` | Extensiones permitidas (ej: `['pdf', 'xlsx', 'png']`). Array vacío = todos los formatos. |
874
- | `maxSizeMB` | `String \| Number` | `5` | Tamaño máximo del archivo en MB. |
875
- | `state` | `String` | `'uphold'` | Estado del botón: `'uphold'` (normal) o `'loader'` (cargando, muestra spinner). |
876
- | `delay` | `String` | `'1.5'` | Segundos de bloqueo del botón tras un clic, para evitar doble envío. |
877
-
878
- **Eventos:**
879
- - `update:modelValue` — emite el `File` seleccionado, o `null` si el archivo no es válido.
880
- - `change` — igual que `update:modelValue`.
881
-
882
- El componente valida automáticamente el tamaño y el formato, mostrando un mensaje de error inline si la validación falla.
883
-
884
- ------
885
-
886
- ## InputFileDownloadStyle
887
-
888
- Botón para descargar archivos con tres estados visuales.
889
-
890
- ```vue
891
- <InputFileDownloadStyle
892
- :state="downloadState"
893
- :disabled="false"
894
- @click="descargarArchivo"
895
- />
896
- ```
897
-
898
- | Prop | Tipo | Default | Descripción |
899
- | ---- | ---- | ------- | ----------- |
900
- | `disabled` | `Boolean` | `false` | Deshabilita el botón. |
901
- | `state` | `String` | `'download'` | Estado: `'download'` (normal), `'loader'` (descargando, spinner), `'complete'` (completado, check verde). |
902
- | `delay` | `String` | `'1.5'` | Segundos de bloqueo del botón tras un clic. |
903
-
904
- **Eventos:**
905
- - `click` — se dispara al hacer clic (solo si no está deshabilitado).
906
-
907
- ------
908
-
909
- ## ButtonStyle
910
-
911
- Botón genérico con bloqueo automático anti-doble-clic.
912
-
913
- ```vue
914
- <ButtonStyle
915
- class="px-4 py-2 bg-indigo-500 text-white rounded"
916
- :disabled="cargando"
917
- @click="guardar"
918
- >
919
- Guardar
920
- </ButtonStyle>
921
- ```
922
-
923
- | Prop | Tipo | Default | Descripción |
924
- | ---- | ---- | ------- | ----------- |
925
- | `type` | `String` | `'button'` | Tipo del botón: `'button'` o `'submit'`. |
926
- | `name` | `String` | `null` | Atributo `name` nativo del `<button>`. |
927
- | `id` | `String` | `null` | Id propio del `<button>`. |
928
- | `autofocus` | `Boolean` | `false` | Enfoca el botón al montarse. |
929
- | `class` | `String` | `''` | Clases CSS del botón (TailwindCSS). |
930
- | `disabled` | `Boolean` | `false` | Deshabilita el botón. |
931
- | `delay` | `String` | `'1.5'` | Segundos de bloqueo automático tras hacer clic (anti-doble-clic). |
932
-
933
- **Eventos:**
934
- - `click` — se dispara al hacer clic (solo si no está deshabilitado ni en periodo de bloqueo).
935
-
936
- Acepta contenido mediante `<slot>` (texto, íconos, etc.).
937
-
938
- > **Nota:** con `type="submit"` funciona como botón de envío nativo dentro de un `<form>`. El bloqueo anti-doble-clic se aplica de forma diferida para no cancelar el envío nativo del formulario.
939
-
940
- ------
941
-
942
- ## LoaderStyle
943
-
944
- Indicador de carga circular animado.
945
-
946
- ```vue
947
- <LoaderStyle message="Procesando datos..." />
948
- ```
949
-
950
- | Prop | Tipo | Default | Descripción |
951
- | ---- | ---- | ------- | ----------- |
952
- | `message` | `String` | `'Cargando...'` | Texto que se muestra debajo del arco animado. |
953
-
954
- El color del arco se controla con el token `LOADER_ARC_STROKE`.
955
-
956
- ------
957
-
958
- ## PaginationStyle
959
-
960
- Paginador con navegación anterior/siguiente, botones de página y resumen de registros.
961
-
962
- ```vue
963
- <PaginationStyle
964
- :total-pages="totalPaginas"
965
- :actual-page="paginaActual"
966
- :records="totalRegistros"
967
- :limit="registrosPorPagina"
968
- :summary="true"
969
- @page="cambiarPagina"
970
- />
971
- ```
972
-
973
- | Prop | Tipo | Default | Descripción |
974
- | ---- | ---- | ------- | ----------- |
975
- | `totalPages` | `Number` | `0` | Total de páginas. |
976
- | `actualPage` | `Number` | `1` | Página actualmente activa. |
977
- | `records` | `Number` | `0` | Total de registros (para el resumen). |
978
- | `limit` | `Number` | `0` | Registros por página (para calcular el rango del resumen). |
979
- | `summary` | `Boolean` | `false` | `true` = muestra el texto "Mostrando X - Y de Z". |
980
-
981
- **Eventos:**
982
- - `page` — emite el número de página a la que se desea navegar.
983
-
984
- El paginador muestra hasta 7 botones con elipsis (`...`) inteligentes cuando hay más páginas.
985
-
986
- ------
987
-
988
- ## ModalStyle
989
-
990
- Ventana modal con `Teleport` al `body`, animaciones de entrada/salida, bloqueo del scroll de fondo y cierre por tecla `Escape`. El estado de apertura se controla con `v-model`.
991
-
992
- ```vue
993
- <ButtonStyle @click="showModal = true">Abrir modal</ButtonStyle>
994
-
995
- <ModalStyle v-model="showModal" title="Detalles" size="lg">
996
- <p>Contenido del modal...</p>
997
-
998
- <template #footer="{ close }">
999
- <div class="flex justify-end gap-2">
1000
- <ButtonStyle class="px-4 py-2 border rounded" @click="close">Cerrar</ButtonStyle>
1001
- </div>
1002
- </template>
1003
- </ModalStyle>
1004
- ```
1005
-
1006
- | Prop | Tipo | Default | Descripción |
1007
- | ---- | ---- | ------- | ----------- |
1008
- | `modelValue` | `Boolean` | `false` | Controla la visibilidad del modal (`v-model`). |
1009
- | `title` | `String` | `'Sin titulo asignado'` | Título mostrado en el header (si no se usa el slot `title`). |
1010
- | `size` | `String` | `'lg'` | Ancho máximo: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'`, `'3xl'`, `'4xl'`, `'5xl'`, `'full'`. |
1011
- | `showCloseButton` | `Boolean` | `true` | Muestra el botón `✕` de cierre en el header. |
1012
- | `closeOnEscape` | `Boolean` | `true` | Permite cerrar el modal con la tecla `Escape`. |
1013
- | `closeOnOverlayClick` | `Boolean` | `true` | Permite cerrar el modal al hacer clic en el fondo. |
1014
-
1015
- **Slots:**
1016
- - `title` — reemplaza el título del header.
1017
- - *default* — contenido principal. Recibe `{ close }` para cerrar desde dentro.
1018
- - `footer` — pie del modal (solo se renderiza si se provee). Recibe `{ close }`.
1019
-
1020
- **Eventos:**
1021
- - `update:modelValue` — emite `false` al cerrarse.
1022
-
1023
- ------
1024
-
1025
- ## AlertStyle (notificaciones tipo toast)
1026
-
1027
- Utilidad JavaScript (no es un componente) para mostrar notificaciones flotantes que se auto-descartan. Disponible como propiedad global `$alertstyle` tras instalar el plugin, o mediante importación directa.
1028
-
1029
- ```js
1030
- // Vía global property (Options API)
1031
- this.$alertstyle.success('Guardado', 'Los cambios se guardaron correctamente')
1032
-
1033
- // Vía importación directa
1034
- import { AlertStyle } from '@luis-angel-martin-dzul/input-styles'
1035
- AlertStyle.error('Error', 'No se pudo completar la operación')
1036
- ```
1037
-
1038
- | Método | Parámetros | Descripción |
1039
- | ------ | ---------- | ----------- |
1040
- | `success(title, message?)` | `String, String` | Notificación verde de éxito. |
1041
- | `error(title, message?)` | `String, String` | Notificación roja de error. |
1042
- | `warning(title, message?)` | `String, String` | Notificación de advertencia. |
1043
- | `info(title, message?)` | `String, String` | Notificación informativa. |
1044
- | `neutral(title, message?)` | `String, String` | Notificación neutral. |
1045
-
1046
- Cada notificación se cierra automáticamente a los 4 segundos o manualmente con su botón `×`.
1047
-
1048
- ------
1049
-
1050
- ## DialogStyle (diálogos modales)
1051
-
1052
- Utilidad JavaScript para diálogos de confirmación, información y carga, con interfaz fluida (encadenable). Disponible como global `$dialogstyle` o por importación directa.
1053
-
1054
- ```js
1055
- // Confirmación
1056
- this.$dialogstyle.confirm('¿Eliminar registro?', 'Esta acción no se puede deshacer')
1057
- .onAccept(() => eliminar())
1058
- .onCancel(() => console.log('Cancelado'))
1059
-
1060
- // Información (un solo botón)
1061
- this.$dialogstyle.info('Operación completada')
1062
- .onConfirm(() => console.log('Aceptado'))
1063
-
1064
- // Carga (bloqueante, se cierra manualmente)
1065
- const loader = this.$dialogstyle.loading('Procesando...', 'Espere un momento')
1066
- loader.updateMessage('Casi listo...')
1067
- loader.close()
1068
- ```
1069
-
1070
- | Método | Retorna | Descripción |
1071
- | ------ | ------- | ----------- |
1072
- | `confirm(title, description?)` | controlador con `.onAccept(cb)` / `.onCancel(cb)` | Diálogo con botones Aceptar / Cancelar. |
1073
- | `info(title, description?)` | controlador con `.onConfirm(cb)` | Diálogo informativo con un solo botón Aceptar. |
1074
- | `loading(title, description?)` | `{ close, updateTitle, updateMessage }` | Diálogo de carga con spinner; se cierra con `.close()`. |
1075
- | `finish(immediate?)` | `Promise` | Cierra el diálogo abierto manualmente. |
1076
-
1077
- > 💡 La tecla `Escape` no cierra el diálogo (hace una animación de "shake"), para forzar una decisión explícita del usuario.
1078
-
1079
- ------
1080
-
1081
- # 🧭 Directivas
1082
-
1083
- ## `v-title-style`
1084
-
1085
- Muestra un **tooltip** ("globo de diálogo") sobre **cualquier etiqueta o componente**, reemplazando al atributo `title` nativo del navegador. Su **fondo es ligeramente transparente** con un sutil desenfoque del contenido que queda detrás.
1086
-
1087
- - **Escritorio (mouse):** aparece al pasar el cursor y se oculta al salir.
1088
- - **Móvil (touch):** se muestra al tocar el elemento y se oculta al tocar de nuevo o fuera.
1089
- - Acepta **texto plano** (fijo o desde una variable reactiva).
1090
-
1091
- Si instalas la librería con `app.use(InputStyles)`, la directiva queda registrada **global** automáticamente. Si la usas en un componente que no instala el plugin, regístrala local: `import { TitleStyle } from '@luis-angel-martin-dzul/input-styles'` y `directives: { titleStyle: TitleStyle }`.
1092
-
1093
- ```vue
1094
- <!-- Texto fijo (nota las comillas simples internas) -->
1095
- <button v-title-style="'Guarda los cambios'">Guardar</button>
1096
-
1097
- <!-- Sobre cualquier etiqueta o componente -->
1098
- <span v-title-style="'Información adicional'">ⓘ</span>
1099
- <button-style v-title-style="'Tooltip translúcido sobre un ButtonStyle'">Con tooltip</button-style>
1100
-
1101
- <!-- Texto dinámico -->
1102
- <button v-title-style="mensajeAyuda">Enviar</button>
1103
- ```
1104
-
1105
- ------
1106
-
1107
- ## UtilStyle (utilidades)
1108
-
1109
- Clase con métodos estáticos de utilidad (formateo, validaciones, manejo de archivos, etc.). Disponible como propiedad global `$utilstyle` tras instalar el plugin, o mediante importación directa.
1110
-
1111
- ```js
1112
- // Vía global property (Options API)
1113
- this.$utilstyle.formatMoney(1234.5)
1114
-
1115
- // Vía importación directa
1116
- import { UtilStyle } from '@luis-angel-martin-dzul/input-styles'
1117
- UtilStyle.getUUID()
1118
- ```
1119
-
1120
- | Método | Parámetros | Descripción |
1121
- | ------ | ---------- | ----------- |
1122
- | `getUUID()` | — | Genera un UUID v4 (usa `crypto.randomUUID` con fallback manual). |
1123
- | `formatMoney(valor, dollar?, digit?)` | `Number, String, Number` | Formatea un número como moneda (ej: `$1,234.56`). |
1124
- | `formatNumber(valor, digit?)` | `Number, Number` | Formatea un número con separadores de miles y decimales fijos. |
1125
- | `formatBytes(bytes, digit?)` | `Number, Number` | Formatea un tamaño en bytes a `B` / `KB` / `MB` legible (ej: `296.0 KB`). |
1126
- | `formatDateShort(dateString)` | `String` | Fecha en formato corto en español (ej: `9 jul 2026`). |
1127
- | `formatDateLong(dateString)` | `String` | Fecha en formato largo en español (ej: `9 de julio de 2026`). |
1128
- | `formatDateTimeShort(dateString, hour12?)` | `String, Boolean` | Fecha y hora en formato corto en español (ej: `9 jul 2026, 12:00 p. m.`). `hour12` (default `true`) alterna entre 12h y 24h. |
1129
- | `formatDateTimeLong(dateString, hour12?)` | `String, Boolean` | Fecha y hora en formato largo en español (ej: `9 de julio de 2026, 12:00 p. m.`). `hour12` (default `true`) alterna entre 12h y 24h. |
1130
- | `getDateTimeNow()` | — | Fecha y hora actual (zona `America/Mexico_City`) en formato `YYYY-MM-DDTHH:mm:ss`. |
1131
- | `porcentaje(valor, total)` | `Number, Number` | Calcula el porcentaje de `valor` respecto a `total` (ej: `50`). Retorna `0` si `total` es `0` o negativo. |
1132
- | `blockKeyNumber(event)` | `Event` | Bloquea las teclas `e`, `E`, `+`, `-` en un `@keydown`. |
1133
- | `blockKeys(event, keys)` | `Event, Array` | Bloquea las teclas indicadas en un `@keydown`. |
1134
- | `validateTel(value, required?)` | `String, Boolean` | Valida un teléfono con formato `999 9 99 99 99`. Acepta prefijo de región opcional `+XXX 999 9 99 99 99` (XXX = 1 a 3 dígitos). Retorna `true` o mensaje de error. |
1135
- | `validateEmail(value, required?)` | `String, Boolean` | Valida un correo con las condiciones mínimas `xx@xx.x`. Retorna `true` o mensaje de error. |
1136
- | `validateFileExtension(file, allowedExtensions?)` | `File, Array` | Valida la extensión del archivo. Retorna `true` o mensaje de error. |
1137
- | `validateFileType(file, allowedTypes?)` | `File, Array` | Valida el MIME type del archivo. Retorna `true` o mensaje de error. |
1138
- | `validateFileSize(file, maxSizeMB?)` | `File, Number` | Valida el tamaño del archivo. Retorna `true` o mensaje de error. |
1139
- | `downloadBlob(blobData, fileName?, mimeType?)` | `Blob, String, String` | Descarga un blob como archivo. |
1140
- | `fileToBase64(file)` | `File` | Convierte un `File` a base64 (async, retorna `Promise`). |
1141
- | `base64ToBlob(base64, mimeType?)` | `String, String` | Convierte una cadena base64 a `Blob`. |
1142
- | `base64ToFile(base64, fileName?, mimeType?)` | `String, String, String` | Convierte una cadena base64 a `File`. |
1143
- | `fileToImage(file)` | `File` | Carga un `File` de imagen en un `HTMLImageElement` (async, retorna `Promise`). Libera el object URL al terminar. |
1144
- | `compressToWebp(file, quality?, maxWidth?)` | `File, Number, Number` | Comprime una imagen a **WebP** en el navegador (async). Ver [Compresión de imágenes a WebP](#compresión-de-imágenes-a-webp). |
1145
-
1146
- > 💡 Los métodos de validación (`validateTel`, `validateEmail`, `validateFile*`) devuelven `true` si el valor es válido, o un `String` con el mensaje de error si no lo es.
1147
-
1148
- ------
1149
-
1150
- # 🖼️ Compresión de imágenes a WebP
1151
-
1152
- Convierte imágenes (PNG, JPEG, etc.) a **WebP** directamente en el navegador —sin subir nada al servidor— usando la Canvas API. Útil para reducir el peso de las imágenes **antes** de enviarlas a tu backend.
1153
-
1154
- El método principal es `UtilStyle.compressToWebp(file, quality, maxWidth)`.
1155
-
1156
- ```js
1157
- // file: Number, quality: 0 a 1 (ej. 0.8 = 80%), maxWidth: px (opcional)
1158
- const res = await UtilStyle.compressToWebp(file, 0.8, 1920)
1159
- ```
1160
-
1161
- | Parámetro | Tipo | Default | Descripción |
1162
- | --------- | ---- | ------- | ----------- |
1163
- | `file` | `File` | — | Archivo de imagen original (PNG/JPEG/etc). |
1164
- | `quality` | `Number` | `0.8` | Calidad de `0` a `1` (ej: `0.8` = 80%). Solo aplica en WebP con pérdida. |
1165
- | `maxWidth` | `Number \| null` | `null` | Si se indica, redimensiona manteniendo proporción. Recomendado para fotos grandes. |
1166
-
1167
- **Retorna** una `Promise` que resuelve a un objeto:
1168
-
1169
- ```js
1170
- {
1171
- blob, // Blob de la imagen WebP (listo para subir o descargar)
1172
- originalSize, // Tamaño original en bytes
1173
- compressedSize, // Tamaño comprimido en bytes
1174
- ratio, // % de ahorro (positivo = más ligero; negativo = más pesado)
1175
- width, height, // Dimensiones finales del WebP
1176
- }
1177
- ```
1178
-
1179
- ## ¿Cuándo usarlo?
1180
-
1181
- ✅ **Úsalo cuando:**
1182
- - Quieras **reducir el peso** de imágenes que suben tus usuarios antes de enviarlas al servidor (ahorra ancho de banda y almacenamiento).
1183
- - Trabajes con **fotos** o imágenes con muchos colores/degradados (es donde WebP más ahorra).
1184
- - Quieras **previsualizar** el ahorro antes de confirmar la subida.
1185
-
1186
- ⚠️ **Evítalo / revísalo cuando:**
1187
- - Necesites soportar **Safari**: su canvas no codifica WebP de forma fiable y puede devolver un PNG disfrazado. El método **detecta esto y lanza un error explícito** en vez de entregar un archivo "mentiroso". Para soporte universal usa un encoder WASM (p. ej. `@jsquash/webp`) como fallback.
1188
- - La imagen sea un **ícono/ilustración plana con pocos colores**: el ahorro de WebP con pérdida es mínimo y puede generar ruido en los bordes. Considera dejarla como PNG.
1189
- - Necesites conservar **metadatos EXIF** (orientación, geolocalización): el canvas los descarta. Pásale `maxWidth` y ten en cuenta que fotos con orientación EXIF podrían rotarse.
1190
-
1191
- > 💡 **Referencia de ahorro** (cifras oficiales del equipo WebP de Google): lossless ≈ 26% menos que PNG; con pérdida 25–34% menos que JPEG a calidad visual equivalente. No hay un porcentaje fijo: depende del contenido. **Calidad recomendada:** 75–85% para fotos (a partir de 75% la diferencia suele ser imperceptible).
1192
-
1193
- ## Uso básico (un archivo)
1194
-
1195
- ```js
1196
- import { UtilStyle } from '@luis-angel-martin-dzul/input-styles'
1197
-
1198
- async function comprimir(file) {
1199
- try {
1200
- const res = await UtilStyle.compressToWebp(file, 0.8, 1920)
1201
-
1202
- console.log(`Ahorro: ${res.ratio}%`)
1203
- console.log(`${UtilStyle.formatBytes(res.originalSize)} → ${UtilStyle.formatBytes(res.compressedSize)}`)
1204
-
1205
- // Subir al servidor
1206
- const formData = new FormData()
1207
- formData.append('imagen', res.blob, file.name.replace(/\.\w+$/, '.webp'))
1208
- // await fetch('/api/upload', { method: 'POST', body: formData })
1209
-
1210
- // O descargar localmente (reutiliza downloadBlob de UtilStyle)
1211
- UtilStyle.downloadBlob(res.blob, 'imagen.webp', 'image/webp')
1212
- } catch (e) {
1213
- console.error(e.message) // p. ej. error de Safari
1214
- }
1215
- }
1216
- ```
1217
-
1218
- ## Uso con validación y múltiples archivos (Options API)
1219
-
1220
- ```vue
1221
- <template>
1222
- <input type="file" accept="image/*" multiple @change="onFiles" />
1223
- <p v-if="error" class="text-red-500">{{ error }}</p>
1224
- </template>
1225
-
1226
- <script>
1227
- export default {
1228
- data() {
1229
- return { error: '', resultados: [] }
1230
- },
1231
- methods: {
1232
- async onFiles(e) {
1233
- this.error = ''
1234
- const files = Array.from(e.target.files || [])
1235
-
1236
- for (const file of files) {
1237
- // Reutiliza las validaciones de UtilStyle
1238
- const extOk = this.$utilstyle.validateFileExtension(file, ['png', 'jpg', 'jpeg'])
1239
- if (extOk !== true) { this.error = extOk; continue }
1240
-
1241
- const sizeOk = this.$utilstyle.validateFileSize(file, 10)
1242
- if (sizeOk !== true) { this.error = sizeOk; continue }
1243
-
1244
- try {
1245
- const res = await this.$utilstyle.compressToWebp(file, 0.8, 1920)
1246
- this.resultados.push(res)
1247
- } catch (err) {
1248
- this.error = err.message
1249
- }
1250
- }
1251
- }
1252
- }
1253
- }
1254
- </script>
1255
- ```
1256
-
1257
- > ℹ️ La librería expone **solo los métodos** de compresión en `UtilStyle`; no incluye un componente visual. Si necesitas una UI lista (selector + comparación de tamaños + descarga), puedes construirla sobre estos métodos como en el ejemplo anterior.
1258
-
1259
- ------
1260
-
1261
- ## Ejemplo de uso completo
1262
-
1263
- ```vue
1264
- <template>
1265
- <div class="p-6 space-y-4 max-w-md">
1266
-
1267
- <InputTextStyle
1268
- v-model="form.nombre"
1269
- label="Nombre completo"
1270
- placeholder="Ej: Juan Pérez"
1271
- :required="true"
1272
- :validate="validaciones.nombre"
1273
- />
1274
-
1275
- <InputNumberStyle
1276
- v-model="form.monto"
1277
- label="Monto"
1278
- numeric="10,2"
1279
- min="0"
1280
- :validate="validaciones.monto"
1281
- />
1282
-
1283
- <InputPasswordStyle
1284
- v-model="form.password"
1285
- label="Contraseña"
1286
- :required="true"
1287
- :validate="validaciones.password"
1288
- />
1289
-
1290
- <SelectStyle
1291
- v-model="form.pais"
1292
- label="País"
1293
- :list="paises"
1294
- return-value="value"
1295
- :validate="validaciones.pais"
1296
- />
1297
-
1298
- <InputDateStyle
1299
- v-model="form.fecha"
1300
- label="Fecha"
1301
- :validate="validaciones.fecha"
1302
- />
1303
-
1304
- <InputDateTimeStyle
1305
- v-model="form.fechaHora"
1306
- label="Fecha y hora"
1307
- :is24h="true"
1308
- :validate="validaciones.fechaHora"
1309
- />
1310
-
1311
- <InputTimeStyle
1312
- v-model="form.hora"
1313
- label="Hora"
1314
- :is24h="true"
1315
- />
1316
-
1317
- <InputCheckStyle
1318
- v-model="form.acepta"
1319
- label="Acepto los términos"
1320
- :required="true"
1321
- />
1322
-
1323
- <ButtonStyle
1324
- class="w-full px-4 py-2 bg-indigo-500 text-white rounded hover:bg-indigo-600"
1325
- @click="enviar"
1326
- >
1327
- Enviar
1328
- </ButtonStyle>
1329
-
1330
- </div>
1331
- </template>
1332
-
1333
- <script setup>
1334
- import { ref } from 'vue'
1335
- import {
1336
- InputTextStyle, InputNumberStyle, InputPasswordStyle,
1337
- SelectStyle, InputDateStyle, InputDateTimeStyle, InputTimeStyle,
1338
- InputCheckStyle, ButtonStyle
1339
- } from '@luis-angel-martin-dzul/input-styles'
1340
-
1341
- const form = ref({
1342
- nombre: '', monto: '', password: '',
1343
- pais: null, fecha: '', fechaHora: '', hora: '', acepta: false
1344
- })
1345
-
1346
- const validaciones = ref({
1347
- nombre: null, monto: null, password: null,
1348
- pais: null, fecha: null, fechaHora: null
1349
- })
1350
-
1351
- const paises = [
1352
- { value: 1, text: 'México' },
1353
- { value: 2, text: 'España' },
1354
- ]
1355
-
1356
- function enviar() {
1357
- validaciones.value.nombre = form.value.nombre.length > 0
1358
- // ... resto de validaciones
1359
- }
1360
- </script>
1361
- ```
1
+ # 📦 Input Style
2
+
3
+ **Librería de componentes y formularios Vue 3**
4
+
5
+ Incluye componentes de inputs, selects, botones, paginación y más, estilizados con **Tailwind CSS 4** y listos para producción.
6
+
7
+ > ⚠️ **Solo compatible con Tailwind CSS 4.** La librería usa la sintaxis y los nombres de utilidades de la v4 (`outline-hidden`, `backdrop-blur-xs`, `z-100`, etc.) y se configura con `@import "tailwindcss"` + `@source`. **No funciona con Tailwind CSS 3** (`tailwind.config.js` / `content`).
8
+
9
+ ------
10
+
11
+ ![Última actualización](https://img.shields.io/badge/Actualizado-19%20de%20Octubre%20del%2026-blue)
12
+ ![Versión](https://img.shields.io/badge/versión-0.0.37-indigo)
13
+ ![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-4.x-38bdf8)
14
+
15
+ ------
16
+
17
+ # 📋 Requisitos
18
+
19
+ | Dependencia | Versión |
20
+ | ----------- | ------- |
21
+ | `vue` | `^3.3.0` |
22
+ | `tailwindcss` | `^4.0.0` |
23
+ | `@tailwindcss/vite` (o `@tailwindcss/postcss`) | `^4.0.0` |
24
+
25
+ ------
26
+
27
+ # 📦 Instalación
28
+
29
+ La librería se distribuye mediante **npm**:
30
+
31
+ ```
32
+ npm install @luis-angel-martin-dzul/input-styles
33
+ ```
34
+
35
+ Si todavía no tienes Tailwind CSS 4 en tu proyecto:
36
+
37
+ ```
38
+ npm install tailwindcss @tailwindcss/vite
39
+ ```
40
+
41
+ ------
42
+
43
+ # 🚀 Uso en Vue + Vite
44
+
45
+ En `main.js` o `main.ts`:
46
+
47
+ ```js
48
+ import { createApp } from 'vue'
49
+ import App from './App.vue'
50
+
51
+ import '@luis-angel-martin-dzul/input-styles/style.css'
52
+ import InputStyle from '@luis-angel-martin-dzul/input-styles'
53
+
54
+ const app = createApp(App)
55
+
56
+ app.use(InputStyle)
57
+ app.mount('#app')
58
+ ```
59
+
60
+ Al instalar el plugin se registran automáticamente todos los componentes y las siguientes **propiedades globales** (disponibles en cualquier componente con la Options API):
61
+
62
+ | Propiedad global | Equivale a | Descripción |
63
+ | ---------------- | ---------- | ----------- |
64
+ | `$alertstyle` | `AlertStyle` | Notificaciones tipo toast (ver [AlertStyle](#alertstyle-notificaciones-tipo-toast)). |
65
+ | `$dialogstyle` | `DialogStyle` | Diálogos de confirmación / info / carga (ver [DialogStyle](#dialogstyle-diálogos-modales)). |
66
+ | `$utilstyle` | `UtilStyle` | Utilidades internas (ej: generación de UUID). |
67
+
68
+ ------
69
+
70
+ # 🎨 Configuración de Tailwind CSS 4
71
+
72
+ Tailwind CSS 4 se configura desde el CSS (no existe `tailwind.config.js` ni la opción `content`). Se necesitan **dos pasos**.
73
+
74
+ **1. Activa el plugin de Tailwind en Vite** (`vite.config.js`):
75
+
76
+ ```js
77
+ import { defineConfig } from 'vite'
78
+ import vue from '@vitejs/plugin-vue'
79
+ import tailwindcss from '@tailwindcss/vite'
80
+
81
+ export default defineConfig({
82
+ plugins: [vue(), tailwindcss()],
83
+ })
84
+ ```
85
+
86
+ **2. Importa Tailwind e indica con `@source` dónde está la librería.** En el CSS principal de tu app (por ejemplo `src/style.css`):
87
+
88
+ ```css
89
+ @import "tailwindcss";
90
+
91
+ /* Tailwind 4 ignora node_modules; sin esta línea no se generan las clases que usa la librería. */
92
+ @source "../node_modules/@luis-angel-martin-dzul/input-styles/dist";
93
+ ```
94
+
95
+ > 💡 La ruta de `@source` es **relativa al archivo CSS** donde la escribes. Ajusta los `../` según su ubicación. Recuerda el `;` al final: es una regla CSS.
96
+
97
+ El CSS propio de la librería (alertas, diálogos y animaciones) se importa aparte, en `main.js`:
98
+
99
+ ```js
100
+ import '@luis-angel-martin-dzul/input-styles/style.css'
101
+ ```
102
+
103
+ **Sin `@source`** los componentes se verán sin estilos o a medias, porque las clases de Tailwind que usan no se generan.
104
+
105
+ # 🎨 Estilos personalizados (Style Registry)
106
+
107
+ La librería expone un sistema de **tokens de estilo configurables** que permite cambiar la apariencia de todos los componentes desde un solo lugar, sin necesidad de modificar el código fuente.
108
+
109
+ Los estilos se configuran **antes de montar la app**, importando `configureStyles` directamente.
110
+
111
+ > ⚠️ **Los tokens deben escribirse con clases de Tailwind CSS 4.** Además, las clases de tus tokens **tienen que ser visibles para Tailwind**: como se definen en tu propio código (`main.js`), quedan cubiertas por el escaneo automático de tu proyecto. Si las defines en un paquete dentro de `node_modules`, agrégalas con `@source`.
112
+ >
113
+ > Nombres que cambiaron respecto a Tailwind 3 y que debes tener en cuenta al personalizar:
114
+ >
115
+ > | Tailwind 3 | Tailwind 4 |
116
+ > | ---------- | ---------- |
117
+ > | `outline-none` | `outline-hidden` (en v4, `outline-none` quita el contorno por completo) |
118
+ > | `shadow-sm` / `shadow` | `shadow-xs` / `shadow-sm` |
119
+ > | `rounded-sm` / `rounded` | `rounded-xs` / `rounded-sm` |
120
+ > | `blur-sm` / `backdrop-blur-sm` | `blur-xs` / `backdrop-blur-xs` |
121
+ > | `ring` (3px) | `ring-3` (`ring` ahora es de 1px) |
122
+ > | `flex-shrink-0` / `flex-grow` | `shrink-0` / `grow` |
123
+ > | `bg-opacity-50` | `bg-black/50` (modificador de opacidad) |
124
+ > | `bg-[--mi-var]` | `bg-(--mi-var)` |
125
+ > | `!px-2` (important al inicio) | `px-2!` (important al final) |
126
+
127
+ ## Opción A — vía `app.use` (recomendado)
128
+
129
+ ```js
130
+ import InputStyle from '@luis-angel-martin-dzul/input-styles'
131
+
132
+ app.use(InputStyle, {
133
+ styles: {
134
+ INPUT_BASE: 'w-full px-0 py-2 bg-transparent border-b focus:outline-hidden ...',
135
+ ICON_VALID: 'text-emerald-500',
136
+ // solo los tokens que quieras cambiar
137
+ }
138
+ })
139
+ ```
140
+
141
+ ## Opción B — importación directa
142
+
143
+ ```js
144
+ import { configureStyles } from '@luis-angel-martin-dzul/input-styles'
145
+
146
+ configureStyles({
147
+ LOADER_ARC_STROKE: '#3B82F6',
148
+ REQUIRED_ASTERISK: 'text-red-500',
149
+ })
150
+ ```
151
+
152
+ ⚠️ `configureStyles()` debe llamarse **antes** de que los componentes se monten. Los cambios no son reactivos después del montaje inicial.
153
+
154
+ ------
155
+
156
+ ## 📋 Referencia completa de tokens
157
+
158
+ ### Tokens auxiliares de label
159
+ _Afecta: todos los componentes que tienen `label` o `description`_
160
+
161
+ Estos tokens controlan partes individuales del label y del texto descriptivo. Al sobrescribir uno, las funciones `inputLabelClass`, `inputCheckLabelClass` e `inputDescriptionClass` lo recogen automáticamente **sin necesidad de sobreescribir la función entera**.
162
+
163
+ | Token | Tipo | Default | Descripción |
164
+ | ----- | ---- | ------- | ----------- |
165
+ | `INPUT_BASE_TITLE` | `string` | `'block text-sm font-medium'` | Clases estructurales del `<label>` de inputs de texto. |
166
+ | `INPUT_CHECK_BASE_TITLE` | `string` | `'block text-sm font-medium'` | Clases estructurales del `<label>` de checkbox, radio y switch. |
167
+ | `LABEL_DISABLED_STATIC` | `string` | `'text-gray-400'` | Color del label y descripción cuando el campo está deshabilitado. |
168
+ | `LABEL_ENABLED_STATIC` | `string` | `'text-gray-700'` | Color del label cuando el campo está habilitado. |
169
+
170
+ ```js
171
+ configureStyles({
172
+ LABEL_DISABLED_STATIC: 'text-gray-400',
173
+ LABEL_ENABLED_STATIC: 'text-gray-900',
174
+ INPUT_BASE_TITLE: 'block font-semibold mb-1 text-sm',
175
+ })
176
+ ```
177
+
178
+ ---
179
+
180
+ ### Inputs de texto
181
+ _Afecta: `InputTextStyle`, `InputNumberStyle`, `InputPasswordStyle`, `TextAreaStyle`, `InputDateStyle`, `InputDateTimeStyle`, `InputTimeStyle`, `SelectStyle`, `InputAutocompleteStyle`_
182
+
183
+ | Token | Tipo | Default | Descripción |
184
+ | ----- | ---- | ------- | ----------- |
185
+ | `INPUT_BASE` | `string` | `'w-full text-gray-800 bg-white px-3 py-1.5 rounded border ...'` | Clases base del `<input>`. No incluye borde ni ring (eso lo maneja `inputValidateClass`). |
186
+ | `inputValidateClass` | `function(validate)` | Ver abajo | Retorna las clases de borde y ring según el estado de validación. `validate`: `true` / `false` / `null`. |
187
+ | `inputLabelClass` | `function(disabled)` | Ver abajo | Retorna las clases del `<label>`. Internamente usa `INPUT_BASE_TITLE`, `LABEL_DISABLED_STATIC` y `LABEL_ENABLED_STATIC`. |
188
+ | `inputDescriptionClass` | `function(validate, alignRight?)` | Ver abajo | Retorna las clases del texto de descripción. Internamente usa `ICON_VALID`, `ICON_INVALID` y `LABEL_DISABLED_STATIC`. |
189
+
190
+ > 💡 Si solo quieres cambiar un color de validación, basta con sobrescribir `ICON_INVALID` o `ICON_VALID` — `inputDescriptionClass` los recoge automáticamente.
191
+
192
+ ```js
193
+ configureStyles({
194
+ ICON_INVALID: 'text-red-500',
195
+ ICON_VALID: 'text-emerald-500',
196
+ })
197
+
198
+ // Override completo de las funciones (cuando necesitas control total)
199
+ configureStyles({
200
+ inputValidateClass: (validate) => {
201
+ if (validate === false) return 'border-red-500 focus:ring-red-200'
202
+ if (validate === true) return 'border-emerald-500 focus:ring-emerald-200'
203
+ return 'border-gray-200 focus:ring-indigo-200'
204
+ },
205
+ inputLabelClass: (disabled) => ['block font-bold mb-2', disabled ? 'text-gray-400' : 'text-gray-900'],
206
+ inputDescriptionClass: (validate, alignRight = true) => [
207
+ 'block w-full text-sm',
208
+ alignRight && 'text-right',
209
+ validate === false ? 'text-red-500' : validate === true ? 'text-emerald-500' : 'text-gray-500',
210
+ ],
211
+ })
212
+ ```
213
+
214
+ ---
215
+
216
+ ### Select
217
+ _Afecta: `SelectStyle`, `InputAutocompleteStyle`_
218
+
219
+ | Token | Tipo | Default | Descripción |
220
+ | ----- | ---- | ------- | ----------- |
221
+ | `SELECT_DISABLED` | `string` | `'w-full px-3 py-1.5 rounded bg-gray-100 ...'` | Clases del input en estado disabled. |
222
+ | `SELECT_DROPDOWN` | `string` | `'absolute z-20 w-full bg-white border ...'` | Clases del contenedor `<ul>` del dropdown. |
223
+ | `SELECT_OPTION_BASE` | `string` | `'px-3 py-1 cursor-pointer text-md'` | Clases base de cada `<li>` del dropdown. |
224
+ | `selectOptionClass` | `function(isSelected, isHighlighted)` | Ver abajo | Retorna clases dinámicas por opción según su estado. |
225
+ | `SELECT_OPTGROUP` | `string` | `'px-2 pt-1 pb-0.5 text-xs font-semibold uppercase ...'` | Clases del encabezado de un grupo de opciones (`optgroup`). |
226
+ | `SELECT_EMPTY` | `string` | `'px-3 py-1 text-gray-400 cursor-not-allowed'` | Clases del mensaje "Sin registros". |
227
+
228
+ ```js
229
+ configureStyles({
230
+ selectOptionClass: (isSelected, isHighlighted) => {
231
+ if (isSelected) return 'bg-indigo-100 text-indigo-900 font-medium'
232
+ if (isHighlighted) return 'bg-indigo-50 text-gray-900'
233
+ return 'hover:bg-indigo-50 text-gray-800'
234
+ },
235
+ })
236
+ ```
237
+
238
+ ---
239
+
240
+ ### Checkbox, Radio y Switch
241
+ _Afecta: `InputCheckStyle`, `InputRadioStyle`, `InputSwitchStyle`_
242
+
243
+ | Token | Tipo | Default | Descripción |
244
+ | ----- | ---- | ------- | ----------- |
245
+ | `inputCheckLabelClass` | `function(disabled)` | Ver abajo | Clases del label de checks. |
246
+ | `CHECK_BOX_BASE` | `string` | `'w-5 h-5 flex items-center justify-center rounded border-2 transition'` | Estructura base del cuadro del checkbox. |
247
+ | `RADIO_BASE` | `string` | `'w-5 h-5 flex items-center justify-center rounded-full transition'` | Estructura base del círculo del radio. |
248
+ | `SWITCH_BASE` | `string` | `'w-10 h-5 relative flex items-center rounded-full transition border-2'` | Estructura base de la pista del switch. |
249
+ | `checkBoxClass` | `function(model, disabled)` | Ver abajo | Clases de color del checkbox según estado activo/disabled. |
250
+ | `radioClass` | `function(model, disabled)` | Ver abajo | Clases de color del radio button según estado. |
251
+ | `switchClass` | `function(model, disabled)` | Ver abajo | Clases de color de la pista del switch. |
252
+ | `switchDotClass` | `function(model, disabled)` | Ver abajo | Clases de color de la bola interior del switch. |
253
+
254
+ ```js
255
+ configureStyles({
256
+ checkBoxClass: (model, disabled) => {
257
+ if (disabled) return model
258
+ ? 'bg-indigo-200 border-indigo-200 cursor-not-allowed'
259
+ : 'bg-gray-100 border-gray-200 cursor-not-allowed'
260
+ return model
261
+ ? 'bg-indigo-500 border-indigo-500 peer-focus:ring-2 peer-focus:ring-indigo-200'
262
+ : 'bg-white border-gray-300 peer-focus:ring-2 peer-focus:ring-indigo-200'
263
+ },
264
+ })
265
+ ```
266
+
267
+ ---
268
+
269
+ ### Paginación
270
+ _Afecta: `PaginationStyle`_
271
+
272
+ | Token | Tipo | Default | Descripción |
273
+ | ----- | ---- | ------- | ----------- |
274
+ | `PAGINATION_NAV_PREV` | `string` | `'bg-white hover:bg-indigo-50 border ...'` | Clases del botón de página anterior. |
275
+ | `PAGINATION_NAV_NEXT` | `string` | `'bg-white hover:bg-indigo-50 border ...'` | Clases del botón de página siguiente. |
276
+ | `PAGINATION_PAGE_BASE` | `string` | `'border px-2.5 py-2 cursor-pointer w-8 relative'` | Clases base de cada botón de página. |
277
+ | `paginationPageClass` | `function(page, actualPage, index)` | Ver abajo | Retorna clases dinámicas por botón de página. |
278
+ | `PAGINATION_SUMMARY` | `string` | `'text-sm text-gray-700'` | Clases del texto de resumen de registros. |
279
+
280
+ ---
281
+
282
+ ### Loader
283
+ _Afecta: `LoaderStyle`_
284
+
285
+ | Token | Tipo | Default | Descripción |
286
+ | ----- | ---- | ------- | ----------- |
287
+ | `LOADER_ARC_STROKE` | `string` | `'#6366F1'` | Color hex del arco animado del loader (indigo-500). |
288
+ | `LOADER_MESSAGE` | `string` | `'text-gray-600 text-lg font-medium'` | Clases del mensaje de carga. |
289
+
290
+ ---
291
+
292
+ ### Iconos y asterisco de requerido
293
+ _Afecta: todos los componentes con `required` o iconos de validación_
294
+
295
+ | Token | Tipo | Default | Descripción |
296
+ | ----- | ---- | ------- | ----------- |
297
+ | `REQUIRED_ASTERISK` | `string` | `'text-red-500'` | Clase de color del asterisco `*` en campos requeridos. |
298
+ | `ICON_VALID` | `string` | `'text-emerald-500'` | Clase de color del ícono de validación correcta. |
299
+ | `ICON_INVALID` | `string` | `'text-red-500'` | Clase de color del ícono de validación errónea. |
300
+ | `ICON_STATIC` | `string` | `'text-indigo-400'` | Clase de color de los íconos estáticos (reloj, calendario, ojo, chevron). |
301
+ | `ICON_SIZE_STATIC` | `string` | `'w-5 h-5'` | Clases de tamaño de todos los íconos. |
302
+
303
+ ---
304
+
305
+ ### File Upload y Download
306
+ _Afecta: `InputFileUploadStyle`, `InputFileDownloadStyle`_
307
+
308
+ | Token | Tipo | Default | Descripción |
309
+ | ----- | ---- | ------- | ----------- |
310
+ | `FILE_BUTTON_BASE` | `string` | `'flex items-center justify-center gap-3 px-4 py-2 border-2 rounded-xl ...'` | Clases estructurales del botón (compartido entre upload y download). |
311
+ | `FILE_BUTTON_ACTIVE` | `string` | `'border-gray-200 bg-white text-gray-700 hover:border-indigo-400 ...'` | Clases del botón en estado normal/activo. |
312
+ | `FILE_BUTTON_DISABLED` | `string` | `'border-gray-200 bg-gray-100 text-gray-400'` | Clases del botón en estado disabled. |
313
+ | `FILE_ICON_BG` | `string` | `'bg-indigo-300'` | Fondo del círculo de ícono en estado activo. |
314
+ | `FILE_ICON_BG_COMPLETE` | `string` | `'bg-emerald-500'` | Fondo del círculo de ícono cuando la descarga está completa. |
315
+ | `FILE_ICON_BG_DISABLED` | `string` | `'bg-gray-300'` | Fondo del círculo de ícono en estado disabled. |
316
+
317
+ ---
318
+
319
+ ### Time Picker
320
+ _Afecta: `InputTimeStyle`_
321
+
322
+ | Token | Tipo | Default | Descripción |
323
+ | ----- | ---- | ------- | ----------- |
324
+ | `TIME_PICKER_WRAPPER` | `string` | `'absolute z-20 left-0 top-full rounded-lg bg-white'` | Contenedor flotante del picker. |
325
+ | `TIME_PICKER_CONTAINER` | `string` | `'border border-gray-300 rounded-lg p-3'` | Borde interior del picker. |
326
+ | `TIME_PICKER_COLUMN` | `string` | `'h-48 overflow-y-auto border border-gray-200 rounded-md ...'` | Columna scrollable de horas/minutos. |
327
+ | `TIME_PICKER_AMPM_COLUMN` | `string` | `'h-48 overflow-y-auto border border-gray-200 rounded-md ...'` | Columna scrollable AM/PM. |
328
+ | `TIME_PICKER_COLUMN_LABEL` | `string` | `'block text-xs font-medium text-gray-500 mb-1 text-center'` | Label de cada columna ("Hora", "Minuto", "AM/PM"). |
329
+ | `TIME_PICKER_ITEM` | `string` | `'px-3 py-1 text-center cursor-pointer hover:bg-indigo-50 ...'` | Fila base de cada ítem del picker. |
330
+ | `timePickerItemClass` | `function(isActive)` | Ver abajo | Clases dinámicas de cada ítem según si está seleccionado. |
331
+
332
+ ------
333
+
334
+ # 🧩 Componentes
335
+
336
+ ## Props comunes
337
+
338
+ La mayoría de los componentes comparten las siguientes props:
339
+
340
+ | Prop | Tipo | Default | Descripción |
341
+ | ---- | ---- | ------- | ----------- |
342
+ | `modelValue` | `String \| Number \| Boolean` | — | Valor del campo (v-model). |
343
+ | `id` | `String` | `null` | Id propio del control. Si se omite, se genera automáticamente (uuid) y se mantiene enlazado con el `<label for>`. |
344
+ | `name` | `String` | `null` | Atributo `name` nativo del control (`input` / `textarea` / `file` / `button`). Útil para envío de formularios, autocompletado y accesibilidad. |
345
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo automáticamente al montarse. |
346
+ | `label` | `String` | `''` | Texto del label del campo. |
347
+ | `description` | `String` | `''` | Texto de ayuda debajo del campo. Cambia de color según el estado de validación. |
348
+ | `placeholder` | `String` | `''` | Placeholder del input. |
349
+ | `disabled` | `Boolean` | `false` | Deshabilita el campo. |
350
+ | `required` | `Boolean` | `false` | Marca el campo como requerido (muestra asterisco `*`). |
351
+ | `validate` | `Boolean \| null` | `null` | Estado de validación: `true` = válido, `false` = inválido, `null` = sin estado. |
352
+ | `mclass` | `String` | `null` | Clases CSS adicionales aplicadas directamente al elemento del input. |
353
+
354
+ ## Eventos comunes
355
+
356
+ | Evento | Payload | Descripción |
357
+ | ------ | ------- | ----------- |
358
+ | `update:modelValue` | `value` | Se dispara al cambiar el valor (permite `v-model`). |
359
+ | `change` | `value` | Se dispara al cambiar el valor. |
360
+ | `blur` | `event` | Se dispara al perder el foco. |
361
+
362
+ ------
363
+
364
+ ## InputTextStyle
365
+
366
+ Input de texto genérico. Soporta tipos `text`, `email`, `search`, `tel`, `url` y `number`.
367
+
368
+ ```vue
369
+ <InputTextStyle
370
+ v-model="nombre"
371
+ label="Nombre"
372
+ placeholder="Escribe tu nombre"
373
+ :required="true"
374
+ :validate="validate"
375
+ description="Campo requerido"
376
+ />
377
+ ```
378
+
379
+ | Prop | Tipo | Default | Descripción |
380
+ | ---- | ---- | ------- | ----------- |
381
+ | `modelValue` | `String \| Number` | `''` | Valor del campo. |
382
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
383
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
384
+ | `autocomplete` | `String` | `null` | Atributo `autocomplete` nativo (ej: `email`, `username`, `off`). |
385
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
386
+ | `readonly` | `Boolean` | `false` | Solo lectura: no editable, pero se envía en el formulario. |
387
+ | `label` | `String` | `''` | |
388
+ | `description` | `String` | `''` | |
389
+ | `placeholder` | `String` | `''` | |
390
+ | `type` | `String` | `'text'` | Tipo del input: `text`, `email`, `search`, `tel`, `url`, `number`. |
391
+ | `maxlength` | `String` | `null` | Longitud máxima de caracteres. |
392
+ | `minlength` | `String` | `null` | Longitud mínima de caracteres. |
393
+ | `min` | `String` | `null` | Valor mínimo (solo cuando `type="number"`, aplicado al perder el foco). |
394
+ | `max` | `String` | `null` | Valor máximo (solo cuando `type="number"`, aplicado al perder el foco). |
395
+ | `disabled` | `Boolean` | `false` | |
396
+ | `required` | `Boolean` | `false` | |
397
+ | `validate` | `Boolean \| null` | `null` | |
398
+ | `mclass` | `String` | `null` | |
399
+
400
+ ------
401
+
402
+ ## InputNumberStyle
403
+
404
+ Input numérico con control preciso de dígitos, decimales, rango y formato de visualización. El valor se emite siempre como string numérico limpio (ej: `"1234.56"`). Al perder el foco se formatea con `toLocaleString`.
405
+
406
+ ```vue
407
+ <InputNumberStyle
408
+ v-model="monto"
409
+ label="Monto"
410
+ numeric="10,2"
411
+ min="0"
412
+ max="9999999999"
413
+ :required="true"
414
+ :validate="validate"
415
+ />
416
+ ```
417
+
418
+ | Prop | Tipo | Default | Descripción |
419
+ | ---- | ---- | ------- | ----------- |
420
+ | `modelValue` | `String \| Number` | `''` | Valor del campo. |
421
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
422
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
423
+ | `autocomplete` | `String` | `null` | Atributo `autocomplete` nativo. |
424
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
425
+ | `readonly` | `Boolean` | `false` | Solo lectura: bloquea la edición y los botones ▲/▼, pero se envía en el formulario. |
426
+ | `label` | `String` | `''` | |
427
+ | `description` | `String` | `''` | |
428
+ | `placeholder` | `String` | `''` | |
429
+ | `numeric` | `String` | `null` | Formato tipo SQL: `"10,2"` → 10 dígitos enteros, 2 decimales. Si se omite, usa `10,2` por defecto. |
430
+ | `min` | `String` | `null` | Valor mínimo (aplicado al perder el foco). Si es `null` o negativo, permite negativos. |
431
+ | `max` | `String` | `null` | Valor máximo (aplicado al perder el foco). |
432
+ | `alignLeft` | `Boolean` | `false` | Posición de los botones de paso (▲/▼) respecto al valor. `false` = botones a la izquierda, valor a la derecha (`Rows \| Input`); `true` = botones a la derecha, valor a la izquierda (`Input \| Rows`). |
433
+ | `disabled` | `Boolean` | `false` | |
434
+ | `required` | `Boolean` | `false` | |
435
+ | `validate` | `Boolean \| null` | `null` | |
436
+ | `mclass` | `String` | `null` | |
437
+
438
+ **Comportamiento:**
439
+ - Incluye botones **caret ▲ / ▼** para aumentar/disminuir el valor en unidades (respetando `min`/`max`). Por defecto se alinean a la izquierda del campo (con el valor a la derecha); con `alignLeft` se mueven a la derecha y el valor pasa a la izquierda. No tienen separador y usan el color global de los iconos (`ICON_STATIC`).
440
+ - Si **mantienes presionado** un botón, tras una breve pausa el valor cambia de forma **continua** hasta soltar.
441
+ - Puedes escribir `0` y luego `.` para empezar los decimales: el punto se conserva a la espera de los dígitos decimales (solo si los decimales están activados en `numeric`).
442
+ - Al **enfocar** el campo, si el valor es `0` (o está vacío) se limpia, para que al escribir reemplaces el valor en lugar de anteponerlo.
443
+ - Al **perder el foco**, si el campo queda vacío o en `0`, se conserva el valor `0` formateado (ej: `"0.00"`), aplicando además los límites `min`/`max`.
444
+
445
+ **Navegación por teclado:**
446
+ - `↑` / `↓` — aumentar/disminuir el valor en unidades, respetando `min`/`max`.
447
+
448
+ ------
449
+
450
+ ## InputPasswordStyle
451
+
452
+ Input de contraseña con toggle para mostrar/ocultar el texto.
453
+
454
+ ```vue
455
+ <InputPasswordStyle
456
+ v-model="password"
457
+ label="Contraseña"
458
+ placeholder="Mínimo 8 caracteres"
459
+ :required="true"
460
+ :validate="validate"
461
+ />
462
+ ```
463
+
464
+ | Prop | Tipo | Default | Descripción |
465
+ | ---- | ---- | ------- | ----------- |
466
+ | `modelValue` | `String \| Number` | `''` | Valor del campo. |
467
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
468
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
469
+ | `autocomplete` | `String` | `null` | Atributo `autocomplete` nativo (ej: `current-password`, `new-password`). |
470
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
471
+ | `readonly` | `Boolean` | `false` | Solo lectura: no editable, pero se envía en el formulario. |
472
+ | `label` | `String` | `''` | |
473
+ | `description` | `String` | `''` | |
474
+ | `placeholder` | `String` | `''` | |
475
+ | `maxlength` | `String` | `null` | Longitud máxima. |
476
+ | `disabled` | `Boolean` | `false` | |
477
+ | `required` | `Boolean` | `false` | |
478
+ | `validate` | `Boolean \| null` | `null` | |
479
+ | `mclass` | `String` | `null` | |
480
+
481
+ ------
482
+
483
+ ## TextAreaStyle
484
+
485
+ Área de texto multi-línea.
486
+
487
+ ```vue
488
+ <TextAreaStyle
489
+ v-model="comentario"
490
+ label="Comentario"
491
+ placeholder="Escribe aquí..."
492
+ row="5"
493
+ :maxlength="500"
494
+ :validate="validate"
495
+ />
496
+ ```
497
+
498
+ | Prop | Tipo | Default | Descripción |
499
+ | ---- | ---- | ------- | ----------- |
500
+ | `modelValue` | `String` | `''` | Valor del campo. |
501
+ | `name` | `String` | `null` | Atributo `name` nativo del `<textarea>`. |
502
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
503
+ | `autocomplete` | `String` | `null` | Atributo `autocomplete` nativo. |
504
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
505
+ | `readonly` | `Boolean` | `false` | Solo lectura: no editable, pero se envía en el formulario. |
506
+ | `label` | `String` | `''` | |
507
+ | `description` | `String` | `''` | |
508
+ | `placeholder` | `String` | `''` | |
509
+ | `row` | `String` | `'3'` | Número de filas visibles del textarea. |
510
+ | `maxlength` | `String` | `null` | Longitud máxima de caracteres. |
511
+ | `disabled` | `Boolean` | `false` | |
512
+ | `required` | `Boolean` | `false` | |
513
+ | `validate` | `Boolean \| null` | `null` | |
514
+ | `mclass` | `String` | `null` | |
515
+
516
+ ------
517
+
518
+ ## SelectStyle
519
+
520
+ Select personalizado con dropdown, navegación por teclado y búsqueda por tipeo. El dropdown se posiciona automáticamente arriba o abajo según el espacio disponible.
521
+
522
+ ```vue
523
+ <SelectStyle
524
+ v-model="pais"
525
+ label="País"
526
+ :list="paises"
527
+ return-value="value"
528
+ :required="true"
529
+ :validate="validate"
530
+ />
531
+ ```
532
+
533
+ ```js
534
+ // Formato del array `list` — lista plana
535
+ const paises = [
536
+ { value: 1, text: 'México' },
537
+ { value: 2, text: 'España' },
538
+ { value: 3, text: 'Argentina' },
539
+ ]
540
+
541
+ // El `value` es opcional: si se omite, se usa el `text` como valor
542
+ const colores = [
543
+ { text: 'Rojo' },
544
+ { text: 'Verde' },
545
+ ]
546
+
547
+ // Lista agrupada — cada grupo tiene un `label` y sus `options`
548
+ const ubicaciones = [
549
+ { label: 'América', options: [{ value: 1, text: 'México' }, { value: 2, text: 'Argentina' }] },
550
+ { label: 'Europa', options: [{ value: 3, text: 'España' }, { value: 4, text: 'Francia' }] },
551
+ ]
552
+ ```
553
+
554
+ | Prop | Tipo | Default | Descripción |
555
+ | ---- | ---- | ------- | ----------- |
556
+ | `modelValue` | `String \| Number \| Object` | — | Valor seleccionado. |
557
+ | `list` | `Array` | `[]` | Array de opciones. Acepta lista plana `{ value, text }` o agrupada `{ label, options: [...] }`. El `value` es opcional: si se omite, se usa el `text` como valor. |
558
+ | `returnValue` | `String` | `'value'` | Qué se emite al seleccionar: `'value'` (item.value), `'text'` (item.text) u `'object'` (el item completo). |
559
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
560
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
561
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
562
+ | `label` | `String` | `''` | |
563
+ | `description` | `String` | `''` | |
564
+ | `wrapOptions` | `Boolean` | `false` | `true` = permite que el texto de cada opción haga salto de línea; `false` = lo trunca con elipsis. |
565
+ | `disabled` | `Boolean` | `false` | |
566
+ | `required` | `Boolean` | `false` | |
567
+ | `validate` | `Boolean \| null` | `null` | |
568
+ | `mclass` | `String` | `null` | |
569
+
570
+ **Navegación por teclado:**
571
+ - `↑` / `↓` — navegar opciones
572
+ - `Enter` — seleccionar la opción resaltada
573
+ - `Escape` — cerrar el dropdown
574
+ - Tipear letras — búsqueda inmediata en la lista
575
+
576
+ ------
577
+
578
+ ## InputAutocompleteStyle
579
+
580
+ Autocomplete basado en `SelectStyle`, pero con el **input editable**: el usuario escribe para filtrar las opciones en vivo. Mantiene el mismo formato de `list` (plana o agrupada) y la misma navegación por teclado y posicionamiento automático del dropdown.
581
+
582
+ Incluye un estado de **carga/búsqueda** controlado por la prop `loading`: cuando es `true`, oculta las opciones y muestra el mensaje _"Buscando coincidencias..."_ — pensado para búsquedas asíncronas donde el listado se resuelve en el servidor.
583
+
584
+ ```vue
585
+ <InputAutocompleteStyle
586
+ v-model="pais"
587
+ label="País"
588
+ :list="paises"
589
+ placeholder="Escribe para buscar"
590
+ :loading="buscando"
591
+ return-value="value"
592
+ :validate="validate"
593
+ @search="onSearch"
594
+ />
595
+ ```
596
+
597
+ ```js
598
+ // El evento @search se emite en cada tecleo; úsalo para disparar una
599
+ // búsqueda asíncrona y controlar la prop `loading`.
600
+ function onSearch(query) {
601
+ buscando.value = true
602
+ fetchPaises(query).then(res => {
603
+ paises.value = res
604
+ buscando.value = false
605
+ })
606
+ }
607
+ ```
608
+
609
+ ```js
610
+ // Formato del array `list` — idéntico a SelectStyle (plana o agrupada)
611
+ const paises = [
612
+ { value: 1, text: 'México' },
613
+ { value: 2, text: 'España' },
614
+ { value: 3, text: 'Argentina' },
615
+ ]
616
+ ```
617
+
618
+ | Prop | Tipo | Default | Descripción |
619
+ | ---- | ---- | ------- | ----------- |
620
+ | `modelValue` | `String \| Number \| Object` | — | Valor seleccionado. |
621
+ | `list` | `Array` | `[]` | Array de opciones. Acepta lista plana `{ value, text }` o agrupada `{ label, options: [...] }`. El `value` es opcional: si se omite, se usa el `text` como valor. |
622
+ | `returnValue` | `String` | `'value'` | Qué se emite al seleccionar: `'value'` (item.value), `'text'` (item.text) u `'object'` (el item completo). |
623
+ | `loading` | `Boolean` | `false` | Cuando es `true`, oculta las opciones y muestra "Buscando coincidencias...". Úsalo mientras resuelves una búsqueda asíncrona. |
624
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
625
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
626
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
627
+ | `label` | `String` | `''` | |
628
+ | `description` | `String` | `''` | |
629
+ | `placeholder` | `String` | `''` | |
630
+ | `wrapOptions` | `Boolean` | `false` | `true` = permite que el texto de cada opción haga salto de línea; `false` = lo trunca con elipsis. |
631
+ | `disabled` | `Boolean` | `false` | |
632
+ | `required` | `Boolean` | `false` | |
633
+ | `validate` | `Boolean \| null` | `null` | |
634
+ | `mclass` | `String` | `null` | |
635
+
636
+ **Eventos:**
637
+ - `search` — se emite en cada cambio del texto escrito, con el `query` actual como payload. Ideal para búsquedas asíncronas.
638
+ - `update:modelValue` / `change` — emiten el valor al seleccionar una opción (según `returnValue`).
639
+ - `blur` — al cerrarse el dropdown.
640
+
641
+ **Comportamiento:**
642
+ - Al abrir (foco o clic en el chevron) muestra todas las opciones; al teclear, filtra por coincidencia de texto (sin distinguir mayúsculas).
643
+ - Al seleccionar una opción, el input muestra su `text`.
644
+ - Si se cierra el dropdown sin seleccionar, el input vuelve a reflejar la selección real (descarta el texto escrito sin confirmar).
645
+
646
+ **Navegación por teclado:**
647
+ - `↑` / `↓` — navegar opciones
648
+ - `Enter` — seleccionar la opción resaltada
649
+ - `Escape` — cerrar el dropdown
650
+
651
+ ------
652
+
653
+ ## InputCheckStyle
654
+
655
+ Checkbox estilizado con label y descripción.
656
+
657
+ ```vue
658
+ <InputCheckStyle
659
+ v-model="aceptaTerminos"
660
+ label="Acepto los términos y condiciones"
661
+ :required="true"
662
+ />
663
+ ```
664
+
665
+ | Prop | Tipo | Default | Descripción |
666
+ | ---- | ---- | ------- | ----------- |
667
+ | `modelValue` | `Boolean \| Array \| String \| Number` | `false` | Estado del check (modo simple) o array de valores seleccionados (modo grupo). |
668
+ | `value` | `String \| Number \| Boolean` | `null` | Valor que aporta este checkbox al grupo. Se usa cuando `modelValue` es un `Array` (checkbox múltiple nativo). |
669
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. Comparte el mismo `name` entre las casillas del grupo. |
670
+ | `id` | `String` | `null` | Id propio del `<input>`; si se omite se genera automáticamente. |
671
+ | `label` | `String` | `''` | |
672
+ | `description` | `String` | `''` | |
673
+ | `disabled` | `Boolean` | `false` | |
674
+ | `required` | `Boolean` | `false` | |
675
+ | `validate` | `Boolean \| null` | `null` | |
676
+ | `mclass` | `String` | `null` | Clases adicionales del cuadro del checkbox. |
677
+
678
+ **Dos modos de uso:**
679
+
680
+ - **Modo simple (por defecto):** con `modelValue` booleano, se comporta como un check `true`/`false`.
681
+
682
+ ```vue
683
+ <InputCheckStyle v-model="acepto" label="Acepto los términos" />
684
+ ```
685
+
686
+ - **Modo grupo nativo:** enlaza varias casillas a un `v-model` de tipo **array**, cada una con su `value` y el mismo `name`. Marcar/desmarcar agrega o quita el `value` del array, y en un `<form>` nativo el navegador envía un `name=<value>` por cada casilla marcada (`FormData.getAll`).
687
+
688
+ ```vue
689
+ <form @submit.prevent="onSubmit">
690
+ <InputCheckStyle v-model="perms" name="perm" value="read" label="Leer" />
691
+ <InputCheckStyle v-model="perms" name="perm" value="write" label="Escribir" />
692
+ <InputCheckStyle v-model="perms" name="perm" value="delete" label="Eliminar" />
693
+ </form>
694
+ ```
695
+
696
+ ```js
697
+ // perms === ['read', 'write'] → FormData del <form>: perm=read, perm=write
698
+ data() { return { perms: ['read', 'write'] } },
699
+ onSubmit(e) { console.log(new FormData(e.target).getAll('perm')) }
700
+ ```
701
+
702
+ **Eventos:** `update:modelValue` y `change` emiten el array actualizado (modo grupo) o el booleano (modo simple).
703
+
704
+ ------
705
+
706
+ ## InputRadioStyle
707
+
708
+ Radio button estilizado con label y descripción.
709
+
710
+ ```vue
711
+ <InputRadioStyle
712
+ v-model="opcionA"
713
+ label="Opción A"
714
+ />
715
+ ```
716
+
717
+ | Prop | Tipo | Default | Descripción |
718
+ | ---- | ---- | ------- | ----------- |
719
+ | `modelValue` | `String \| Number \| Boolean` | `false` | Valor seleccionado del grupo (modo grupo) o estado del toggle (modo booleano). |
720
+ | `value` | `String \| Number \| Boolean` | `null` | Valor que representa este radio dentro del grupo. Si se define, actúa como radio nativo real (`type="radio"`); si se omite, se comporta como toggle booleano. |
721
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. **Requerido** para agrupar varios radios en modo nativo. |
722
+ | `id` | `String` | `null` | Id propio del `<input>`; si se omite se genera automáticamente. |
723
+ | `label` | `String` | `''` | |
724
+ | `description` | `String` | `''` | |
725
+ | `disabled` | `Boolean` | `false` | |
726
+ | `required` | `Boolean` | `false` | |
727
+ | `validate` | `Boolean \| null` | `null` | |
728
+ | `mclass` | `String` | `null` | Clases adicionales del círculo del radio. |
729
+
730
+ **Dos modos de uso:**
731
+
732
+ - **Modo booleano (por defecto):** sin `value`, se comporta como un toggle `true`/`false`. Útil para una sola opción.
733
+
734
+ ```vue
735
+ <InputRadioStyle v-model="acepto" label="Acepto" />
736
+ ```
737
+
738
+ - **Modo grupo nativo:** al definir `value` en cada radio y compartir el mismo `name`, se comportan como un grupo de radios reales (`type="radio"`): seleccionar uno deselecciona los demás, `v-model` guarda el `value` elegido y en un `<form>` nativo el navegador envía `name=<value seleccionado>`.
739
+
740
+ ```vue
741
+ <form @submit.prevent="onSubmit">
742
+ <InputRadioStyle v-model="plan" name="plan" value="free" label="Free" />
743
+ <InputRadioStyle v-model="plan" name="plan" value="pro" label="Pro" />
744
+ <InputRadioStyle v-model="plan" name="plan" value="enterprise" label="Enterprise" />
745
+ </form>
746
+ ```
747
+
748
+ ```js
749
+ // plan === 'pro' → FormData del <form> contiene plan=pro
750
+ onSubmit(e) { console.log(new FormData(e.target).get('plan')) }
751
+ ```
752
+
753
+ **Eventos:** `update:modelValue` y `change` emiten el `value` seleccionado (modo grupo) o el booleano (modo toggle).
754
+
755
+ ------
756
+
757
+ ## InputSwitchStyle
758
+
759
+ Toggle switch estilizado.
760
+
761
+ ```vue
762
+ <InputSwitchStyle
763
+ v-model="activo"
764
+ label="Activar notificaciones"
765
+ />
766
+ ```
767
+
768
+ | Prop | Tipo | Default | Descripción |
769
+ | ---- | ---- | ------- | ----------- |
770
+ | `modelValue` | `Boolean` | `false` | Estado del switch. |
771
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
772
+ | `id` | `String` | `null` | Id propio del `<input>`; si se omite se genera automáticamente. |
773
+ | `label` | `String` | `''` | |
774
+ | `description` | `String` | `''` | |
775
+ | `disabled` | `Boolean` | `false` | |
776
+ | `required` | `Boolean` | `false` | |
777
+ | `validate` | `Boolean \| null` | `null` | |
778
+ | `mclass` | `String` | `null` | Clases adicionales de la pista del switch. |
779
+
780
+ ------
781
+
782
+ ## InputDateStyle
783
+
784
+ Selector de fecha con máscara `dd/mm/aaaa`, navegación por teclado entre secciones y calendario visual nativo (sin dependencias externas). El valor se emite en formato `YYYY-MM-DD`.
785
+
786
+ El calendario incluye vistas de días, meses y años, y un botón **Hoy** para navegar rápidamente a la fecha actual.
787
+
788
+ ```vue
789
+ <InputDateStyle
790
+ v-model="fechaNacimiento"
791
+ label="Fecha de nacimiento"
792
+ min="1900-01-01"
793
+ max="2100-12-31"
794
+ :validate="validate"
795
+ />
796
+ ```
797
+
798
+ | Prop | Tipo | Default | Descripción |
799
+ | ---- | ---- | ------- | ----------- |
800
+ | `modelValue` | `String \| Date` | — | Valor de la fecha en formato `YYYY-MM-DD` o `Date`. |
801
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
802
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
803
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
804
+ | `label` | `String` | `''` | |
805
+ | `description` | `String` | `''` | |
806
+ | `min` | `String` | `'1900-01-01'` | Fecha mínima seleccionable en el calendario (formato `YYYY-MM-DD`). |
807
+ | `max` | `String` | `'2100-12-31'` | Fecha máxima seleccionable en el calendario (formato `YYYY-MM-DD`). |
808
+ | `disabledDates` | `Array` | `[]` | Fechas deshabilitadas. Array de strings `'YYYY-MM-DD'` o objetos `Date`. |
809
+ | `disabled` | `Boolean` | `false` | |
810
+ | `readonly` | `Boolean` | `false` | Solo lectura: muestra el valor pero bloquea la edición por teclado. |
811
+ | `required` | `Boolean` | `false` | |
812
+ | `validate` | `Boolean \| null` | `null` | |
813
+ | `mclass` | `String` | `null` | |
814
+
815
+ **Valor emitido:** `'YYYY-MM-DD'` (string) o `''` si la fecha es inválida o está vacía.
816
+
817
+ **Navegación por teclado:**
818
+ - Escribe los dígitos y el foco avanza automáticamente entre día → mes → año
819
+ - `↑` / `↓` — incrementar/decrementar la sección activa (la fecha resultante se ajusta a los límites `min`/`max`)
820
+ - `←` / `→` — mover entre secciones
821
+ - `Tab` / `Shift + Tab` — mover entre secciones (al llegar al extremo, el foco sale del campo)
822
+ - `Backspace` / `Delete` — limpiar la sección activa
823
+ - Clic en el ícono 📅 — abre/cierra el calendario visual
824
+
825
+ ------
826
+
827
+ ## InputTimeStyle
828
+
829
+ Selector de hora con picker de columnas scrollables. Soporta formato 12h (AM/PM) o 24h. El valor se emite en formato `HH:mm`.
830
+
831
+ ```vue
832
+ <InputTimeStyle
833
+ v-model="horaInicio"
834
+ label="Hora de inicio"
835
+ min="08:00"
836
+ max="18:00"
837
+ :is24h="true"
838
+ :validate="validate"
839
+ />
840
+ ```
841
+
842
+ | Prop | Tipo | Default | Descripción |
843
+ | ---- | ---- | ------- | ----------- |
844
+ | `modelValue` | `String` | — | Valor de la hora en formato `HH:mm`. |
845
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
846
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
847
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
848
+ | `label` | `String` | `''` | |
849
+ | `description` | `String` | `''` | |
850
+ | `min` | `String` | `null` | Hora mínima (ej: `'08:00'`). |
851
+ | `max` | `String` | `null` | Hora máxima (ej: `'18:00'`). |
852
+ | `is24h` | `Boolean` | `false` | `true` = formato 24h, `false` = formato 12h con selector AM/PM. |
853
+ | `disabled` | `Boolean` | `false` | |
854
+ | `required` | `Boolean` | `false` | |
855
+ | `validate` | `Boolean \| null` | `null` | |
856
+ | `mclass` | `String` | `null` | |
857
+
858
+ **Valor emitido:** `'HH:mm'` en formato 24h siempre, independientemente del modo de visualización.
859
+
860
+ ------
861
+
862
+ ## InputDateTimeStyle
863
+
864
+ Selector combinado de fecha y hora con máscara `dd/mm/aaaa hh:mm` (o `dd/mm/aaaa hh:mm a.m.` en modo 12h), calendario visual nativo y picker de columnas scrollables para la hora. El valor se emite en formato `YYYY-MM-DDTHH:mm:00.000`.
865
+
866
+ ```vue
867
+ <InputDateTimeStyle
868
+ v-model="fechaHora"
869
+ label="Fecha y hora"
870
+ min="2000-01-01"
871
+ max="2100-12-31"
872
+ :is24h="true"
873
+ :validate="validate"
874
+ />
875
+ ```
876
+
877
+ | Prop | Tipo | Default | Descripción |
878
+ | ---- | ---- | ------- | ----------- |
879
+ | `modelValue` | `String \| Date` | — | Valor en formato `YYYY-MM-DDTHH:mm:00.000` o `Date`. |
880
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input>`. |
881
+ | `id` | `String` | `null` | Id propio; si se omite se genera y se enlaza al `<label>`. |
882
+ | `autofocus` | `Boolean` | `false` | Enfoca el campo al montarse. |
883
+ | `label` | `String` | `''` | |
884
+ | `description` | `String` | `''` | |
885
+ | `min` | `String` | `'1900-01-01'` | Fecha mínima seleccionable (formato `YYYY-MM-DD`). |
886
+ | `max` | `String` | `'2100-12-31'` | Fecha máxima seleccionable (formato `YYYY-MM-DD`). |
887
+ | `disabledDates` | `Array` | `[]` | Fechas deshabilitadas. Array de strings `'YYYY-MM-DD'` o `Date`. |
888
+ | `is24h` | `Boolean` | `false` | `true` = formato 24h, `false` = formato 12h con selector a.m./p.m. |
889
+ | `disabled` | `Boolean` | `false` | |
890
+ | `readonly` | `Boolean` | `false` | Solo lectura. |
891
+ | `required` | `Boolean` | `false` | |
892
+ | `validate` | `Boolean \| null` | `null` | |
893
+ | `mclass` | `String` | `null` | |
894
+
895
+ **Valor emitido:** `'YYYY-MM-DDTHH:mm:00.000'` (string) o `''` si la fecha/hora es inválida o está vacía.
896
+
897
+ **Comportamiento del picker:**
898
+ - El calendario permanece abierto al seleccionar la fecha, para que puedas ajustar también la hora.
899
+ - El selector de hora embebido en el picker usa las mismas columnas scrollables que `InputTimeStyle`.
900
+ - Botón **Ahora** — establece la fecha y hora actuales de un solo clic.
901
+
902
+ **Navegación por teclado:**
903
+ - Escribe los dígitos y el foco avanza automáticamente: día → mes → año → hora → minuto → a.m./p.m.
904
+ - `↑` / `↓` — incrementar/decrementar la sección activa (la fecha se ajusta a los límites `min`/`max`)
905
+ - `←` / `→` — mover entre secciones
906
+ - `Tab` / `Shift + Tab` — mover entre secciones (al llegar al extremo, el foco sale del campo)
907
+ - `Backspace` / `Delete` — limpiar la sección activa
908
+ - `a` / `p` — cambiar entre a.m. y p.m. (solo modo 12h)
909
+ - Clic en el ícono 📅 — abre/cierra el picker
910
+
911
+ ------
912
+
913
+ ## InputFileUploadStyle
914
+
915
+ Botón para adjuntar archivos con validación de tamaño y formato.
916
+
917
+ ```vue
918
+ <InputFileUploadStyle
919
+ v-model="archivo"
920
+ :formats="['pdf', 'docx']"
921
+ max-size-m-b="10"
922
+ :state="uploadState"
923
+ />
924
+ ```
925
+
926
+ | Prop | Tipo | Default | Descripción |
927
+ | ---- | ---- | ------- | ----------- |
928
+ | `modelValue` | `File` | `null` | Archivo seleccionado. |
929
+ | `name` | `String` | `null` | Atributo `name` nativo del `<input type="file">`. |
930
+ | `id` | `String` | `null` | Id propio del `<input type="file">`. |
931
+ | `disabled` | `Boolean` | `false` | Deshabilita el botón. |
932
+ | `formats` | `Array` | `[]` | Extensiones permitidas (ej: `['pdf', 'xlsx', 'png']`). Array vacío = todos los formatos. |
933
+ | `maxSizeMB` | `String \| Number` | `5` | Tamaño máximo del archivo en MB. |
934
+ | `state` | `String` | `'uphold'` | Estado del botón: `'uphold'` (normal) o `'loader'` (cargando, muestra spinner). |
935
+ | `delay` | `String` | `'1.5'` | Segundos de bloqueo del botón tras un clic, para evitar doble envío. |
936
+
937
+ **Eventos:**
938
+ - `update:modelValue` — emite el `File` seleccionado, o `null` si el archivo no es válido.
939
+ - `change` — igual que `update:modelValue`.
940
+
941
+ El componente valida automáticamente el tamaño y el formato, mostrando un mensaje de error inline si la validación falla.
942
+
943
+ ------
944
+
945
+ ## InputFileDownloadStyle
946
+
947
+ Botón para descargar archivos con tres estados visuales.
948
+
949
+ ```vue
950
+ <InputFileDownloadStyle
951
+ :state="downloadState"
952
+ :disabled="false"
953
+ @click="descargarArchivo"
954
+ />
955
+ ```
956
+
957
+ | Prop | Tipo | Default | Descripción |
958
+ | ---- | ---- | ------- | ----------- |
959
+ | `disabled` | `Boolean` | `false` | Deshabilita el botón. |
960
+ | `state` | `String` | `'download'` | Estado: `'download'` (normal), `'loader'` (descargando, spinner), `'complete'` (completado, check verde). |
961
+ | `delay` | `String` | `'1.5'` | Segundos de bloqueo del botón tras un clic. |
962
+
963
+ **Eventos:**
964
+ - `click` — se dispara al hacer clic (solo si no está deshabilitado).
965
+
966
+ ------
967
+
968
+ ## ButtonStyle
969
+
970
+ Botón genérico con bloqueo automático anti-doble-clic.
971
+
972
+ ```vue
973
+ <ButtonStyle
974
+ class="px-4 py-2 bg-indigo-500 text-white rounded"
975
+ :disabled="cargando"
976
+ @click="guardar"
977
+ >
978
+ Guardar
979
+ </ButtonStyle>
980
+ ```
981
+
982
+ | Prop | Tipo | Default | Descripción |
983
+ | ---- | ---- | ------- | ----------- |
984
+ | `type` | `String` | `'button'` | Tipo del botón: `'button'` o `'submit'`. |
985
+ | `name` | `String` | `null` | Atributo `name` nativo del `<button>`. |
986
+ | `id` | `String` | `null` | Id propio del `<button>`. |
987
+ | `autofocus` | `Boolean` | `false` | Enfoca el botón al montarse. |
988
+ | `class` | `String` | `''` | Clases CSS del botón (TailwindCSS). |
989
+ | `disabled` | `Boolean` | `false` | Deshabilita el botón. |
990
+ | `delay` | `String` | `'1.5'` | Segundos de bloqueo automático tras hacer clic (anti-doble-clic). |
991
+
992
+ **Eventos:**
993
+ - `click` — se dispara al hacer clic (solo si no está deshabilitado ni en periodo de bloqueo).
994
+
995
+ Acepta contenido mediante `<slot>` (texto, íconos, etc.).
996
+
997
+ > **Nota:** con `type="submit"` funciona como botón de envío nativo dentro de un `<form>`. El bloqueo anti-doble-clic se aplica de forma diferida para no cancelar el envío nativo del formulario.
998
+
999
+ ------
1000
+
1001
+ ## LoaderStyle
1002
+
1003
+ Indicador de carga circular animado.
1004
+
1005
+ ```vue
1006
+ <LoaderStyle message="Procesando datos..." />
1007
+ ```
1008
+
1009
+ | Prop | Tipo | Default | Descripción |
1010
+ | ---- | ---- | ------- | ----------- |
1011
+ | `message` | `String` | `'Cargando...'` | Texto que se muestra debajo del arco animado. |
1012
+
1013
+ El color del arco se controla con el token `LOADER_ARC_STROKE`.
1014
+
1015
+ ------
1016
+
1017
+ ## PaginationStyle
1018
+
1019
+ Paginador con navegación anterior/siguiente, botones de página y resumen de registros.
1020
+
1021
+ ```vue
1022
+ <PaginationStyle
1023
+ :total-pages="totalPaginas"
1024
+ :actual-page="paginaActual"
1025
+ :records="totalRegistros"
1026
+ :limit="registrosPorPagina"
1027
+ :summary="true"
1028
+ @page="cambiarPagina"
1029
+ />
1030
+ ```
1031
+
1032
+ | Prop | Tipo | Default | Descripción |
1033
+ | ---- | ---- | ------- | ----------- |
1034
+ | `totalPages` | `Number` | `0` | Total de páginas. |
1035
+ | `actualPage` | `Number` | `1` | Página actualmente activa. |
1036
+ | `records` | `Number` | `0` | Total de registros (para el resumen). |
1037
+ | `limit` | `Number` | `0` | Registros por página (para calcular el rango del resumen). |
1038
+ | `summary` | `Boolean` | `false` | `true` = muestra el texto "Mostrando X - Y de Z". |
1039
+
1040
+ **Eventos:**
1041
+ - `page` — emite el número de página a la que se desea navegar.
1042
+
1043
+ El paginador muestra hasta 7 botones con elipsis (`...`) inteligentes cuando hay más páginas.
1044
+
1045
+ ------
1046
+
1047
+ ## ModalStyle
1048
+
1049
+ Ventana modal con `Teleport` al `body`, animaciones de entrada/salida, bloqueo del scroll de fondo y cierre por tecla `Escape`. El estado de apertura se controla con `v-model`.
1050
+
1051
+ ```vue
1052
+ <ButtonStyle @click="showModal = true">Abrir modal</ButtonStyle>
1053
+
1054
+ <ModalStyle v-model="showModal" title="Detalles" size="lg">
1055
+ <p>Contenido del modal...</p>
1056
+
1057
+ <template #footer="{ close }">
1058
+ <div class="flex justify-end gap-2">
1059
+ <ButtonStyle class="px-4 py-2 border rounded" @click="close">Cerrar</ButtonStyle>
1060
+ </div>
1061
+ </template>
1062
+ </ModalStyle>
1063
+ ```
1064
+
1065
+ | Prop | Tipo | Default | Descripción |
1066
+ | ---- | ---- | ------- | ----------- |
1067
+ | `modelValue` | `Boolean` | `false` | Controla la visibilidad del modal (`v-model`). |
1068
+ | `title` | `String` | `'Sin titulo asignado'` | Título mostrado en el header (si no se usa el slot `title`). |
1069
+ | `size` | `String` | `'lg'` | Ancho máximo: `'sm'`, `'md'`, `'lg'`, `'xl'`, `'2xl'`, `'3xl'`, `'4xl'`, `'5xl'`, `'full'`. |
1070
+ | `showCloseButton` | `Boolean` | `true` | Muestra el botón `✕` de cierre en el header. |
1071
+ | `closeOnEscape` | `Boolean` | `true` | Permite cerrar el modal con la tecla `Escape`. |
1072
+ | `closeOnOverlayClick` | `Boolean` | `true` | Permite cerrar el modal al hacer clic en el fondo. |
1073
+
1074
+ **Slots:**
1075
+ - `title` — reemplaza el título del header.
1076
+ - *default* — contenido principal. Recibe `{ close }` para cerrar desde dentro.
1077
+ - `footer` — pie del modal (solo se renderiza si se provee). Recibe `{ close }`.
1078
+
1079
+ **Eventos:**
1080
+ - `update:modelValue` — emite `false` al cerrarse.
1081
+
1082
+ ------
1083
+
1084
+ ## AlertStyle (notificaciones tipo toast)
1085
+
1086
+ Utilidad JavaScript (no es un componente) para mostrar notificaciones flotantes que se auto-descartan. Disponible como propiedad global `$alertstyle` tras instalar el plugin, o mediante importación directa.
1087
+
1088
+ ```js
1089
+ // Vía global property (Options API)
1090
+ this.$alertstyle.success('Guardado', 'Los cambios se guardaron correctamente')
1091
+
1092
+ // Vía importación directa
1093
+ import { AlertStyle } from '@luis-angel-martin-dzul/input-styles'
1094
+ AlertStyle.error('Error', 'No se pudo completar la operación')
1095
+ ```
1096
+
1097
+ | Método | Parámetros | Descripción |
1098
+ | ------ | ---------- | ----------- |
1099
+ | `success(title, message?)` | `String, String` | Notificación verde de éxito. |
1100
+ | `error(title, message?)` | `String, String` | Notificación roja de error. |
1101
+ | `warning(title, message?)` | `String, String` | Notificación de advertencia. |
1102
+ | `info(title, message?)` | `String, String` | Notificación informativa. |
1103
+ | `neutral(title, message?)` | `String, String` | Notificación neutral. |
1104
+
1105
+ Cada notificación se cierra automáticamente a los 4 segundos o manualmente con su botón `×`.
1106
+
1107
+ ------
1108
+
1109
+ ## DialogStyle (diálogos modales)
1110
+
1111
+ Utilidad JavaScript para diálogos de confirmación, información y carga, con interfaz fluida (encadenable). Disponible como global `$dialogstyle` o por importación directa.
1112
+
1113
+ ```js
1114
+ // Confirmación
1115
+ this.$dialogstyle.confirm('¿Eliminar registro?', 'Esta acción no se puede deshacer')
1116
+ .onAccept(() => eliminar())
1117
+ .onCancel(() => console.log('Cancelado'))
1118
+
1119
+ // Información (un solo botón)
1120
+ this.$dialogstyle.info('Operación completada')
1121
+ .onConfirm(() => console.log('Aceptado'))
1122
+
1123
+ // Carga (bloqueante, se cierra manualmente)
1124
+ const loader = this.$dialogstyle.loading('Procesando...', 'Espere un momento')
1125
+ loader.updateMessage('Casi listo...')
1126
+ loader.close()
1127
+ ```
1128
+
1129
+ | Método | Retorna | Descripción |
1130
+ | ------ | ------- | ----------- |
1131
+ | `confirm(title, description?)` | controlador con `.onAccept(cb)` / `.onCancel(cb)` | Diálogo con botones Aceptar / Cancelar. |
1132
+ | `info(title, description?)` | controlador con `.onConfirm(cb)` | Diálogo informativo con un solo botón Aceptar. |
1133
+ | `loading(title, description?)` | `{ close, updateTitle, updateMessage }` | Diálogo de carga con spinner; se cierra con `.close()`. |
1134
+ | `finish(immediate?)` | `Promise` | Cierra el diálogo abierto manualmente. |
1135
+
1136
+ > 💡 La tecla `Escape` no cierra el diálogo (hace una animación de "shake"), para forzar una decisión explícita del usuario.
1137
+
1138
+ ------
1139
+
1140
+ # 🧭 Directivas
1141
+
1142
+ ## `v-title-style`
1143
+
1144
+ Muestra un **tooltip** ("globo de diálogo") sobre **cualquier etiqueta o componente**, reemplazando al atributo `title` nativo del navegador. Su **fondo es sky y semitransparente**, para no ocultar del todo el contenido que queda detrás.
1145
+
1146
+ - **Escritorio (mouse):** aparece al pasar el cursor y se oculta al salir.
1147
+ - **Móvil (touch):** se muestra al tocar el elemento y se oculta al tocar de nuevo o fuera.
1148
+ - Acepta **texto plano** (fijo o desde una variable reactiva).
1149
+
1150
+ Si instalas la librería con `app.use(InputStyles)`, la directiva queda registrada **global** automáticamente. Si la usas en un componente que no instala el plugin, regístrala local: `import { TitleStyle } from '@luis-angel-martin-dzul/input-styles'` y `directives: { titleStyle: TitleStyle }`.
1151
+
1152
+ ```vue
1153
+ <!-- Texto fijo (nota las comillas simples internas) -->
1154
+ <button v-title-style="'Guarda los cambios'">Guardar</button>
1155
+
1156
+ <!-- Sobre cualquier etiqueta o componente -->
1157
+ <span v-title-style="'Información adicional'">ⓘ</span>
1158
+ <button-style v-title-style="'Tooltip translúcido sobre un ButtonStyle'">Con tooltip</button-style>
1159
+
1160
+ <!-- Texto dinámico -->
1161
+ <button v-title-style="mensajeAyuda">Enviar</button>
1162
+ ```
1163
+
1164
+ ------
1165
+
1166
+ ## UtilStyle (utilidades)
1167
+
1168
+ Clase con métodos estáticos de utilidad (formateo, validaciones, manejo de archivos, etc.). Disponible como propiedad global `$utilstyle` tras instalar el plugin, o mediante importación directa.
1169
+
1170
+ ```js
1171
+ // Vía global property (Options API)
1172
+ this.$utilstyle.formatMoney(1234.5)
1173
+
1174
+ // Vía importación directa
1175
+ import { UtilStyle } from '@luis-angel-martin-dzul/input-styles'
1176
+ UtilStyle.getUUID()
1177
+ ```
1178
+
1179
+ | Método | Parámetros | Descripción |
1180
+ | ------ | ---------- | ----------- |
1181
+ | `getUUID()` | — | Genera un UUID v4 (usa `crypto.randomUUID` con fallback manual). |
1182
+ | `formatMoney(valor, dollar?, digit?)` | `Number, String, Number` | Formatea un número como moneda (ej: `$1,234.56`). |
1183
+ | `formatNumber(valor, digit?)` | `Number, Number` | Formatea un número con separadores de miles y decimales fijos. |
1184
+ | `formatBytes(bytes, digit?)` | `Number, Number` | Formatea un tamaño en bytes a `B` / `KB` / `MB` legible (ej: `296.0 KB`). |
1185
+ | `formatDateShort(dateString)` | `String` | Fecha en formato corto en español (ej: `9 jul 2026`). |
1186
+ | `formatDateLong(dateString)` | `String` | Fecha en formato largo en español (ej: `9 de julio de 2026`). |
1187
+ | `formatDateTimeShort(dateString, hour12?)` | `String, Boolean` | Fecha y hora en formato corto en español (ej: `9 jul 2026, 12:00 p. m.`). `hour12` (default `true`) alterna entre 12h y 24h. |
1188
+ | `formatDateTimeLong(dateString, hour12?)` | `String, Boolean` | Fecha y hora en formato largo en español (ej: `9 de julio de 2026, 12:00 p. m.`). `hour12` (default `true`) alterna entre 12h y 24h. |
1189
+ | `getDateTimeNow()` | — | Fecha y hora actual (zona `America/Mexico_City`) en formato `YYYY-MM-DDTHH:mm:ss`. |
1190
+ | `porcentaje(valor, total)` | `Number, Number` | Calcula el porcentaje de `valor` respecto a `total` (ej: `50`). Retorna `0` si `total` es `0` o negativo. |
1191
+ | `blockKeyNumber(event)` | `Event` | Bloquea las teclas `e`, `E`, `+`, `-` en un `@keydown`. |
1192
+ | `blockKeys(event, keys)` | `Event, Array` | Bloquea las teclas indicadas en un `@keydown`. |
1193
+ | `validateTel(value, required?)` | `String, Boolean` | Valida un teléfono con formato `999 9 99 99 99`. Acepta prefijo de región opcional `+XXX 999 9 99 99 99` (XXX = 1 a 3 dígitos). Retorna `true` o mensaje de error. |
1194
+ | `validateEmail(value, required?)` | `String, Boolean` | Valida un correo con las condiciones mínimas `xx@xx.x`. Retorna `true` o mensaje de error. |
1195
+ | `validateFileExtension(file, allowedExtensions?)` | `File, Array` | Valida la extensión del archivo. Retorna `true` o mensaje de error. |
1196
+ | `validateFileType(file, allowedTypes?)` | `File, Array` | Valida el MIME type del archivo. Retorna `true` o mensaje de error. |
1197
+ | `validateFileSize(file, maxSizeMB?)` | `File, Number` | Valida el tamaño del archivo. Retorna `true` o mensaje de error. |
1198
+ | `downloadBlob(blobData, fileName?, mimeType?)` | `Blob, String, String` | Descarga un blob como archivo. |
1199
+ | `fileToBase64(file)` | `File` | Convierte un `File` a base64 (async, retorna `Promise`). |
1200
+ | `base64ToBlob(base64, mimeType?)` | `String, String` | Convierte una cadena base64 a `Blob`. |
1201
+ | `base64ToFile(base64, fileName?, mimeType?)` | `String, String, String` | Convierte una cadena base64 a `File`. |
1202
+ | `fileToImage(file)` | `File` | Carga un `File` de imagen en un `HTMLImageElement` (async, retorna `Promise`). Libera el object URL al terminar. |
1203
+ | `compressToWebp(file, quality?, maxWidth?)` | `File, Number, Number` | Comprime una imagen a **WebP** en el navegador (async). Ver [Compresión de imágenes a WebP](#compresión-de-imágenes-a-webp). |
1204
+
1205
+ > 💡 Los métodos de validación (`validateTel`, `validateEmail`, `validateFile*`) devuelven `true` si el valor es válido, o un `String` con el mensaje de error si no lo es.
1206
+
1207
+ ------
1208
+
1209
+ # 🖼️ Compresión de imágenes a WebP
1210
+
1211
+ Convierte imágenes (PNG, JPEG, etc.) a **WebP** directamente en el navegador —sin subir nada al servidor— usando la Canvas API. Útil para reducir el peso de las imágenes **antes** de enviarlas a tu backend.
1212
+
1213
+ El método principal es `UtilStyle.compressToWebp(file, quality, maxWidth)`.
1214
+
1215
+ ```js
1216
+ // file: Number, quality: 0 a 1 (ej. 0.8 = 80%), maxWidth: px (opcional)
1217
+ const res = await UtilStyle.compressToWebp(file, 0.8, 1920)
1218
+ ```
1219
+
1220
+ | Parámetro | Tipo | Default | Descripción |
1221
+ | --------- | ---- | ------- | ----------- |
1222
+ | `file` | `File` | — | Archivo de imagen original (PNG/JPEG/etc). |
1223
+ | `quality` | `Number` | `0.8` | Calidad de `0` a `1` (ej: `0.8` = 80%). Solo aplica en WebP con pérdida. |
1224
+ | `maxWidth` | `Number \| null` | `null` | Si se indica, redimensiona manteniendo proporción. Recomendado para fotos grandes. |
1225
+
1226
+ **Retorna** una `Promise` que resuelve a un objeto:
1227
+
1228
+ ```js
1229
+ {
1230
+ blob, // Blob de la imagen WebP (listo para subir o descargar)
1231
+ originalSize, // Tamaño original en bytes
1232
+ compressedSize, // Tamaño comprimido en bytes
1233
+ ratio, // % de ahorro (positivo = más ligero; negativo = más pesado)
1234
+ width, height, // Dimensiones finales del WebP
1235
+ }
1236
+ ```
1237
+
1238
+ ## ¿Cuándo usarlo?
1239
+
1240
+ ✅ **Úsalo cuando:**
1241
+ - Quieras **reducir el peso** de imágenes que suben tus usuarios antes de enviarlas al servidor (ahorra ancho de banda y almacenamiento).
1242
+ - Trabajes con **fotos** o imágenes con muchos colores/degradados (es donde WebP más ahorra).
1243
+ - Quieras **previsualizar** el ahorro antes de confirmar la subida.
1244
+
1245
+ ⚠️ **Evítalo / revísalo cuando:**
1246
+ - Necesites soportar **Safari**: su canvas no codifica WebP de forma fiable y puede devolver un PNG disfrazado. El método **detecta esto y lanza un error explícito** en vez de entregar un archivo "mentiroso". Para soporte universal usa un encoder WASM (p. ej. `@jsquash/webp`) como fallback.
1247
+ - La imagen sea un **ícono/ilustración plana con pocos colores**: el ahorro de WebP con pérdida es mínimo y puede generar ruido en los bordes. Considera dejarla como PNG.
1248
+ - Necesites conservar **metadatos EXIF** (orientación, geolocalización): el canvas los descarta. Pásale `maxWidth` y ten en cuenta que fotos con orientación EXIF podrían rotarse.
1249
+
1250
+ > 💡 **Referencia de ahorro** (cifras oficiales del equipo WebP de Google): lossless ≈ 26% menos que PNG; con pérdida 25–34% menos que JPEG a calidad visual equivalente. No hay un porcentaje fijo: depende del contenido. **Calidad recomendada:** 75–85% para fotos (a partir de 75% la diferencia suele ser imperceptible).
1251
+
1252
+ ## Uso básico (un archivo)
1253
+
1254
+ ```js
1255
+ import { UtilStyle } from '@luis-angel-martin-dzul/input-styles'
1256
+
1257
+ async function comprimir(file) {
1258
+ try {
1259
+ const res = await UtilStyle.compressToWebp(file, 0.8, 1920)
1260
+
1261
+ console.log(`Ahorro: ${res.ratio}%`)
1262
+ console.log(`${UtilStyle.formatBytes(res.originalSize)} → ${UtilStyle.formatBytes(res.compressedSize)}`)
1263
+
1264
+ // Subir al servidor
1265
+ const formData = new FormData()
1266
+ formData.append('imagen', res.blob, file.name.replace(/\.\w+$/, '.webp'))
1267
+ // await fetch('/api/upload', { method: 'POST', body: formData })
1268
+
1269
+ // O descargar localmente (reutiliza downloadBlob de UtilStyle)
1270
+ UtilStyle.downloadBlob(res.blob, 'imagen.webp', 'image/webp')
1271
+ } catch (e) {
1272
+ console.error(e.message) // p. ej. error de Safari
1273
+ }
1274
+ }
1275
+ ```
1276
+
1277
+ ## Uso con validación y múltiples archivos (Options API)
1278
+
1279
+ ```vue
1280
+ <template>
1281
+ <input type="file" accept="image/*" multiple @change="onFiles" />
1282
+ <p v-if="error" class="text-red-500">{{ error }}</p>
1283
+ </template>
1284
+
1285
+ <script>
1286
+ export default {
1287
+ data() {
1288
+ return { error: '', resultados: [] }
1289
+ },
1290
+ methods: {
1291
+ async onFiles(e) {
1292
+ this.error = ''
1293
+ const files = Array.from(e.target.files || [])
1294
+
1295
+ for (const file of files) {
1296
+ // Reutiliza las validaciones de UtilStyle
1297
+ const extOk = this.$utilstyle.validateFileExtension(file, ['png', 'jpg', 'jpeg'])
1298
+ if (extOk !== true) { this.error = extOk; continue }
1299
+
1300
+ const sizeOk = this.$utilstyle.validateFileSize(file, 10)
1301
+ if (sizeOk !== true) { this.error = sizeOk; continue }
1302
+
1303
+ try {
1304
+ const res = await this.$utilstyle.compressToWebp(file, 0.8, 1920)
1305
+ this.resultados.push(res)
1306
+ } catch (err) {
1307
+ this.error = err.message
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+ }
1313
+ </script>
1314
+ ```
1315
+
1316
+ > ℹ️ La librería expone **solo los métodos** de compresión en `UtilStyle`; no incluye un componente visual. Si necesitas una UI lista (selector + comparación de tamaños + descarga), puedes construirla sobre estos métodos como en el ejemplo anterior.
1317
+
1318
+ ------
1319
+
1320
+ ## Ejemplo de uso completo
1321
+
1322
+ ```vue
1323
+ <template>
1324
+ <div class="p-6 space-y-4 max-w-md">
1325
+
1326
+ <InputTextStyle
1327
+ v-model="form.nombre"
1328
+ label="Nombre completo"
1329
+ placeholder="Ej: Juan Pérez"
1330
+ :required="true"
1331
+ :validate="validaciones.nombre"
1332
+ />
1333
+
1334
+ <InputNumberStyle
1335
+ v-model="form.monto"
1336
+ label="Monto"
1337
+ numeric="10,2"
1338
+ min="0"
1339
+ :validate="validaciones.monto"
1340
+ />
1341
+
1342
+ <InputPasswordStyle
1343
+ v-model="form.password"
1344
+ label="Contraseña"
1345
+ :required="true"
1346
+ :validate="validaciones.password"
1347
+ />
1348
+
1349
+ <SelectStyle
1350
+ v-model="form.pais"
1351
+ label="País"
1352
+ :list="paises"
1353
+ return-value="value"
1354
+ :validate="validaciones.pais"
1355
+ />
1356
+
1357
+ <InputDateStyle
1358
+ v-model="form.fecha"
1359
+ label="Fecha"
1360
+ :validate="validaciones.fecha"
1361
+ />
1362
+
1363
+ <InputDateTimeStyle
1364
+ v-model="form.fechaHora"
1365
+ label="Fecha y hora"
1366
+ :is24h="true"
1367
+ :validate="validaciones.fechaHora"
1368
+ />
1369
+
1370
+ <InputTimeStyle
1371
+ v-model="form.hora"
1372
+ label="Hora"
1373
+ :is24h="true"
1374
+ />
1375
+
1376
+ <InputCheckStyle
1377
+ v-model="form.acepta"
1378
+ label="Acepto los términos"
1379
+ :required="true"
1380
+ />
1381
+
1382
+ <ButtonStyle
1383
+ class="w-full px-4 py-2 bg-indigo-500 text-white rounded hover:bg-indigo-600"
1384
+ @click="enviar"
1385
+ >
1386
+ Enviar
1387
+ </ButtonStyle>
1388
+
1389
+ </div>
1390
+ </template>
1391
+
1392
+ <script setup>
1393
+ import { ref } from 'vue'
1394
+ import {
1395
+ InputTextStyle, InputNumberStyle, InputPasswordStyle,
1396
+ SelectStyle, InputDateStyle, InputDateTimeStyle, InputTimeStyle,
1397
+ InputCheckStyle, ButtonStyle
1398
+ } from '@luis-angel-martin-dzul/input-styles'
1399
+
1400
+ const form = ref({
1401
+ nombre: '', monto: '', password: '',
1402
+ pais: null, fecha: '', fechaHora: '', hora: '', acepta: false
1403
+ })
1404
+
1405
+ const validaciones = ref({
1406
+ nombre: null, monto: null, password: null,
1407
+ pais: null, fecha: null, fechaHora: null
1408
+ })
1409
+
1410
+ const paises = [
1411
+ { value: 1, text: 'México' },
1412
+ { value: 2, text: 'España' },
1413
+ ]
1414
+
1415
+ function enviar() {
1416
+ validaciones.value.nombre = form.value.nombre.length > 0
1417
+ // ... resto de validaciones
1418
+ }
1419
+ </script>
1420
+ ```