@moni-labs/moni-ui 0.4.13 → 0.4.14
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/custom-elements.json +91 -81
- package/dist/browser/moni-ui.iife.min.js +1537 -1384
- package/dist/browser/moni-ui.iife.min.js.br +0 -0
- package/dist/browser/moni-ui.iife.min.js.gz +0 -0
- package/dist/browser/moni-ui.iife.min.js.map +1 -1
- package/dist/browser/moni-ui.min.js +2703 -2481
- package/dist/browser/moni-ui.min.js.br +0 -0
- package/dist/browser/moni-ui.min.js.gz +0 -0
- package/dist/browser/moni-ui.min.js.map +1 -1
- package/dist/cdn/chunks/{class-map-DsL1Jr2T.js → class-map-QyT9W7rZ.js} +1 -1
- package/dist/cdn/chunks/{decorate-DxJYS4gw.js → decorate-B7wZvwrn.js} +629 -543
- package/dist/cdn/chunks/if-defined-5hr_ccFw.js +1 -0
- package/dist/cdn/chunks/{live-D52JqeKf.js → live-Hm1Di_ND.js} +1 -1
- package/dist/cdn/components/moni-app-bar.js +1 -1
- package/dist/cdn/components/moni-badge.js +1 -1
- package/dist/cdn/components/moni-bottom-sheet.js +1 -1
- package/dist/cdn/components/moni-button-group.js +1 -1
- package/dist/cdn/components/moni-button-segment.js +1 -1
- package/dist/cdn/components/moni-button.js +1 -1
- package/dist/cdn/components/moni-card.js +1 -1
- package/dist/cdn/components/moni-carousel.js +1 -1
- package/dist/cdn/components/moni-checkbox.js +1 -1
- package/dist/cdn/components/moni-chip.js +1 -1
- package/dist/cdn/components/moni-color-field.js +1 -1
- package/dist/cdn/components/moni-context-menu.js +1 -1
- package/dist/cdn/components/moni-dialog.js +1 -1
- package/dist/cdn/components/moni-divider.js +1 -1
- package/dist/cdn/components/moni-expansion.js +1 -1
- package/dist/cdn/components/moni-fab-menu.js +1 -1
- package/dist/cdn/components/moni-fab.js +1 -1
- package/dist/cdn/components/moni-file-field.js +1 -1
- package/dist/cdn/components/moni-icon.js +1 -1
- package/dist/cdn/components/moni-list-item.js +1 -1
- package/dist/cdn/components/moni-list.js +1 -1
- package/dist/cdn/components/moni-loading-indicator.js +1 -1
- package/dist/cdn/components/moni-menu-item.js +1 -1
- package/dist/cdn/components/moni-menu.js +1 -1
- package/dist/cdn/components/moni-morph-modal.js +3 -2
- package/dist/cdn/components/moni-nav-item.js +1 -1
- package/dist/cdn/components/moni-nav.js +1 -1
- package/dist/cdn/components/moni-progress.js +1 -1
- package/dist/cdn/components/moni-radio.js +1 -1
- package/dist/cdn/components/moni-ripple.js +1 -1
- package/dist/cdn/components/moni-segmented-button.js +1 -1
- package/dist/cdn/components/moni-select-option.js +1 -1
- package/dist/cdn/components/moni-select.js +512 -473
- package/dist/cdn/components/moni-shape.js +1 -1
- package/dist/cdn/components/moni-side-sheet.js +1 -1
- package/dist/cdn/components/moni-slider.js +1 -1
- package/dist/cdn/components/moni-snackbar.js +1 -1
- package/dist/cdn/components/moni-split-button.js +1 -1
- package/dist/cdn/components/moni-step.js +1 -1
- package/dist/cdn/components/moni-stepper.js +1 -1
- package/dist/cdn/components/moni-switch.js +1 -1
- package/dist/cdn/components/moni-tab.js +1 -1
- package/dist/cdn/components/moni-tabs.js +1 -1
- package/dist/cdn/components/moni-text-field.js +122 -95
- package/dist/cdn/components/moni-textarea.js +52 -52
- package/dist/cdn/components/moni-time-picker.js +1 -1
- package/dist/cdn/components/moni-toolbar.js +1 -1
- package/dist/cdn/components/moni-tooltip.js +1 -1
- package/dist/cdn/components/moni-typography.js +1 -1
- package/dist/cdn/importmap.json +98 -98
- package/dist/components/_base/field-styles.d.ts.map +1 -1
- package/dist/components/_base/field-styles.js +662 -576
- package/dist/components/moni-morph-modal.d.ts +10 -0
- package/dist/components/moni-morph-modal.d.ts.map +1 -1
- package/dist/components/moni-morph-modal.js +323 -217
- package/dist/components/moni-select.d.ts +16 -16
- package/dist/components/moni-select.d.ts.map +1 -1
- package/dist/components/moni-select.js +797 -727
- package/dist/components/moni-text-field.d.ts +15 -15
- package/dist/components/moni-text-field.d.ts.map +1 -1
- package/dist/components/moni-text-field.js +224 -180
- package/dist/components/moni-textarea.d.ts +13 -13
- package/dist/components/moni-textarea.d.ts.map +1 -1
- package/dist/components/moni-textarea.js +127 -122
- package/docs/api.json +97 -88
- package/docs/assets/moni-morph-modal/autoHeight--false.png +0 -0
- package/docs/assets/moni-morph-modal/autoHeight--true.png +0 -0
- package/docs/components/morph-modal.md +16 -7
- package/docs/components/select.md +74 -74
- package/docs/components/text-field.md +75 -66
- package/docs/components/textarea.md +51 -51
- package/docs/llms.txt +3 -3
- package/package.json +1 -1
- package/src/components/_base/field-styles.ts +723 -637
- package/src/components/moni-morph-modal.test.ts +490 -413
- package/src/components/moni-morph-modal.ts +2274 -2162
- package/src/components/moni-select.test.ts +489 -466
- package/src/components/moni-select.ts +1963 -1815
- package/src/components/moni-text-field.test.ts +366 -342
- package/src/components/moni-text-field.ts +717 -645
- package/src/components/moni-textarea.test.ts +201 -197
- package/src/components/moni-textarea.ts +476 -460
- package/dist/cdn/chunks/if-defined-DdzvcoI7.js +0 -1
|
@@ -1,460 +1,476 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file components/moni-textarea.ts
|
|
3
|
-
* @package @moni-labs/moni-ui
|
|
4
|
-
* @license MIT
|
|
5
|
-
* @contributors Moni Labs & Contributors
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { css, html, nothing } from
|
|
9
|
-
import { customElement, property, query } from
|
|
10
|
-
import { ifDefined } from
|
|
11
|
-
import { classMap } from
|
|
12
|
-
import { MoniElement, sharedStyles, fieldStyles } from
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Componente Material Design 3 Textarea (Área de texto).
|
|
18
|
-
*
|
|
19
|
-
* Un campo de entrada de texto multilínea diseñado para recolectar cantidades mayores de texto,
|
|
20
|
-
* como comentarios, descripciones o mensajes.
|
|
21
|
-
*
|
|
22
|
-
* **Referencia a la especificación M3:** `m3-docs/components/text-fields/specs.md`
|
|
23
|
-
*
|
|
24
|
-
* **Arquitectura visual:**
|
|
25
|
-
* Comparte exactamente el mismo contenedor `.field` y motor de estilos que
|
|
26
|
-
* `<moni-text-field>`, pero internamente renderiza un `<textarea>` nativo en lugar
|
|
27
|
-
* de un `<input>`. Esto asegura la consistencia visual en todos los elementos del formulario
|
|
28
|
-
* en cuanto a etiquetas flotantes, texto de ayuda, estados de error e iconos.
|
|
29
|
-
*
|
|
30
|
-
* **Contador de caracteres:**
|
|
31
|
-
* Si se establece el atributo `maxlength`, el área de texto muestra automáticamente
|
|
32
|
-
* un contador de caracteres (`{longitud actual} / {maxlength}`) ubicado en el
|
|
33
|
-
* borde final (trailing) del área de texto de soporte (abajo a la derecha). Esto puede
|
|
34
|
-
* suprimirse estableciendo el atributo `no-counter`.
|
|
35
|
-
*
|
|
36
|
-
* **Gestión del estado:**
|
|
37
|
-
* Este componente es puramente visual y representacional. Refleja los atributos
|
|
38
|
-
* hacia el textarea nativo, pero NO adjunta listeners internos para `@input` o
|
|
39
|
-
* `@change`. Los consumidores deben adjuntar listeners estándar del DOM directamente
|
|
40
|
-
* a este elemento para capturar la entrada del usuario, tal como lo harían con un
|
|
41
|
-
* textarea nativo.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```html
|
|
45
|
-
* <!-- Textarea llenado (filled) estándar -->
|
|
46
|
-
* <moni-textarea label="Descripción" rows="4"></moni-textarea>
|
|
47
|
-
*
|
|
48
|
-
* <!-- Textarea contorneado (outlined) con contador de caracteres -->
|
|
49
|
-
* <moni-textarea
|
|
50
|
-
* variant="outlined"
|
|
51
|
-
* label="Biografía"
|
|
52
|
-
* maxlength="160"
|
|
53
|
-
* ></moni-textarea>
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* @csspart field - El contenedor div `.field` exterior.
|
|
57
|
-
* @csspart input - El elemento `<textarea>` nativo.
|
|
58
|
-
* @csspart label - El elemento `<label>` flotante.
|
|
59
|
-
* @csspart helper - El área de texto de ayuda/error.
|
|
60
|
-
* @csspart counter - El elemento contador de caracteres.
|
|
61
|
-
*/
|
|
62
|
-
@customElement(
|
|
63
|
-
export class MoniTextarea extends MoniElement {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file components/moni-textarea.ts
|
|
3
|
+
* @package @moni-labs/moni-ui
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @contributors Moni Labs & Contributors
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { css, html, nothing } from "lit";
|
|
9
|
+
import { customElement, property, query } from "lit/decorators.js";
|
|
10
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
11
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
12
|
+
import { MoniElement, sharedStyles, fieldStyles } from "./_base/index.js";
|
|
13
|
+
import "./moni-icon.js";
|
|
14
|
+
import "./moni-progress.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Componente Material Design 3 Textarea (Área de texto).
|
|
18
|
+
*
|
|
19
|
+
* Un campo de entrada de texto multilínea diseñado para recolectar cantidades mayores de texto,
|
|
20
|
+
* como comentarios, descripciones o mensajes.
|
|
21
|
+
*
|
|
22
|
+
* **Referencia a la especificación M3:** `m3-docs/components/text-fields/specs.md`
|
|
23
|
+
*
|
|
24
|
+
* **Arquitectura visual:**
|
|
25
|
+
* Comparte exactamente el mismo contenedor `.field` y motor de estilos que
|
|
26
|
+
* `<moni-text-field>`, pero internamente renderiza un `<textarea>` nativo en lugar
|
|
27
|
+
* de un `<input>`. Esto asegura la consistencia visual en todos los elementos del formulario
|
|
28
|
+
* en cuanto a etiquetas flotantes, texto de ayuda, estados de error e iconos.
|
|
29
|
+
*
|
|
30
|
+
* **Contador de caracteres:**
|
|
31
|
+
* Si se establece el atributo `maxlength`, el área de texto muestra automáticamente
|
|
32
|
+
* un contador de caracteres (`{longitud actual} / {maxlength}`) ubicado en el
|
|
33
|
+
* borde final (trailing) del área de texto de soporte (abajo a la derecha). Esto puede
|
|
34
|
+
* suprimirse estableciendo el atributo `no-counter`.
|
|
35
|
+
*
|
|
36
|
+
* **Gestión del estado:**
|
|
37
|
+
* Este componente es puramente visual y representacional. Refleja los atributos
|
|
38
|
+
* hacia el textarea nativo, pero NO adjunta listeners internos para `@input` o
|
|
39
|
+
* `@change`. Los consumidores deben adjuntar listeners estándar del DOM directamente
|
|
40
|
+
* a este elemento para capturar la entrada del usuario, tal como lo harían con un
|
|
41
|
+
* textarea nativo.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```html
|
|
45
|
+
* <!-- Textarea llenado (filled) estándar -->
|
|
46
|
+
* <moni-textarea label="Descripción" rows="4"></moni-textarea>
|
|
47
|
+
*
|
|
48
|
+
* <!-- Textarea contorneado (outlined) con contador de caracteres -->
|
|
49
|
+
* <moni-textarea
|
|
50
|
+
* variant="outlined"
|
|
51
|
+
* label="Biografía"
|
|
52
|
+
* maxlength="160"
|
|
53
|
+
* ></moni-textarea>
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @csspart field - El contenedor div `.field` exterior.
|
|
57
|
+
* @csspart input - El elemento `<textarea>` nativo.
|
|
58
|
+
* @csspart label - El elemento `<label>` flotante.
|
|
59
|
+
* @csspart helper - El área de texto de ayuda/error.
|
|
60
|
+
* @csspart counter - El elemento contador de caracteres.
|
|
61
|
+
*/
|
|
62
|
+
@customElement("moni-textarea")
|
|
63
|
+
export class MoniTextarea extends MoniElement {
|
|
64
|
+
static formAssociated = true;
|
|
65
|
+
private _internals: ElementInternals;
|
|
66
|
+
|
|
67
|
+
constructor() {
|
|
68
|
+
super();
|
|
69
|
+
this._internals = this.attachInternals();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* El nombre del textarea, enviado con los datos del formulario.
|
|
74
|
+
* @type {string}
|
|
75
|
+
*/
|
|
76
|
+
@property({ reflect: true }) name = "";
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* El texto de la etiqueta flotante.
|
|
80
|
+
* @type {string}
|
|
81
|
+
*/
|
|
82
|
+
@property({ reflect: true }) label = "";
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Variante visual del área de texto.
|
|
86
|
+
* @type {'filled' | 'outlined'}
|
|
87
|
+
* @default 'filled'
|
|
88
|
+
*/
|
|
89
|
+
@property({ reflect: true }) variant: "filled" | "outlined" = "filled";
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Define las dimensiones del área de texto.
|
|
93
|
+
* @type {'small' | 'medium' | 'large' | 'extra'}
|
|
94
|
+
* @default 'medium'
|
|
95
|
+
*/
|
|
96
|
+
@property({ reflect: true })
|
|
97
|
+
size: "small" | "medium" | "large" | "extra" = "medium";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Forma del radio del borde (border-radius) del campo.
|
|
101
|
+
* @type {'round' | 'small-round' | 'square' | 'no-round'}
|
|
102
|
+
* @default 'no-round'
|
|
103
|
+
*/
|
|
104
|
+
@property({ reflect: true })
|
|
105
|
+
shape: "round" | "small-round" | "square" | "no-round" = "no-round";
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Nombre del icono inicial (leading) (Material Symbols).
|
|
109
|
+
* @type {string}
|
|
110
|
+
*/
|
|
111
|
+
@property({ reflect: true }) icon = "";
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Nombre del icono final (trailing) (Material Symbols).
|
|
115
|
+
* @type {string}
|
|
116
|
+
*/
|
|
117
|
+
@property({ reflect: true, attribute: "trailing-icon" }) trailingIcon = "";
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Prefijo de texto corto mostrado antes del valor del input.
|
|
121
|
+
* @type {string}
|
|
122
|
+
*/
|
|
123
|
+
@property({ reflect: true }) prefix = "";
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Sufijo de texto corto mostrado después del valor del input.
|
|
127
|
+
* @type {string}
|
|
128
|
+
*/
|
|
129
|
+
@property({ reflect: true }) suffix = "";
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Número por defecto de líneas de texto visibles.
|
|
133
|
+
* @type {number}
|
|
134
|
+
* @default 3
|
|
135
|
+
*/
|
|
136
|
+
@property({ type: Number, reflect: true }) rows = 3;
|
|
137
|
+
|
|
138
|
+
/** Hace que el textarea crezca y se reduzca según su contenido. */
|
|
139
|
+
@property({ type: Boolean, reflect: true }) autosize = false;
|
|
140
|
+
|
|
141
|
+
/** Número máximo de filas visibles cuando `autosize` está activo. Cero no impone límite. */
|
|
142
|
+
@property({ type: Number, reflect: true, attribute: "max-rows" }) maxRows = 0;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Número máximo de caracteres permitidos en el área de texto.
|
|
146
|
+
* También habilita la visualización del contador de caracteres a menos que `noCounter` sea true.
|
|
147
|
+
* @type {number | null}
|
|
148
|
+
*/
|
|
149
|
+
@property({ type: Number, reflect: true }) maxlength: number | null = null;
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* Restricciones nativas. Antes sólo `maxlength` llegaba al `<textarea>`
|
|
153
|
+
* interno, así que un campo obligatorio o de sólo lectura obligaba a
|
|
154
|
+
* abandonar el componente y usar un control nativo.
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
/** Marca el campo como obligatorio y lo integra en la validación del formulario. */
|
|
158
|
+
@property({ type: Boolean, reflect: true }) required = false;
|
|
159
|
+
|
|
160
|
+
/** Impide editar el valor sin sacarlo del envío ni atenuarlo como `disabled`. */
|
|
161
|
+
@property({ type: Boolean, reflect: true }) readonly = false;
|
|
162
|
+
|
|
163
|
+
/** Mínimo de caracteres aceptados. */
|
|
164
|
+
@property({ type: Number, reflect: true }) minlength: number | null = null;
|
|
165
|
+
|
|
166
|
+
/** Pista de autocompletado del navegador. */
|
|
167
|
+
@property({ reflect: true }) autocomplete = "";
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Oculta la visualización del contador de caracteres cuando se establece `maxlength`.
|
|
171
|
+
* @type {boolean}
|
|
172
|
+
*/
|
|
173
|
+
@property({ type: Boolean, reflect: true, attribute: "no-counter" })
|
|
174
|
+
noCounter = false;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Si es true, muestra un indicador de carga (progreso circular) al final.
|
|
178
|
+
* @type {boolean}
|
|
179
|
+
*/
|
|
180
|
+
@property({ type: Boolean, reflect: true }) loading = false;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Deshabilita el área de texto.
|
|
184
|
+
* @type {boolean}
|
|
185
|
+
*/
|
|
186
|
+
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Texto de ayuda mostrado debajo del campo.
|
|
190
|
+
* @type {string}
|
|
191
|
+
*/
|
|
192
|
+
@property({ reflect: true }) helper = "";
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Texto de error mostrado debajo del campo cuando `error` es true.
|
|
196
|
+
* Sobrescribe el texto de ayuda.
|
|
197
|
+
* @type {string}
|
|
198
|
+
*/
|
|
199
|
+
@property({ reflect: true, attribute: "error-text" }) errorText = "";
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Si es true, establece el campo en un estado de error.
|
|
203
|
+
* @type {boolean}
|
|
204
|
+
*/
|
|
205
|
+
@property({ type: Boolean, reflect: true }) error = false;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* El valor actual del área de texto.
|
|
209
|
+
* @type {string}
|
|
210
|
+
*/
|
|
211
|
+
@property({ reflect: true }) value = "";
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Texto de marcador de posición (placeholder) mostrado cuando el textarea está vacío y la etiqueta es flotante.
|
|
215
|
+
* @type {string}
|
|
216
|
+
*/
|
|
217
|
+
@property({ reflect: true }) placeholder = "";
|
|
218
|
+
|
|
219
|
+
@query("textarea") private _input!: HTMLTextAreaElement;
|
|
220
|
+
private _resizeObserver?: ResizeObserver;
|
|
221
|
+
|
|
222
|
+
override connectedCallback() {
|
|
223
|
+
super.connectedCallback();
|
|
224
|
+
this._resizeObserver = new ResizeObserver(() => this._resizeTextarea());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
override disconnectedCallback() {
|
|
228
|
+
this._resizeObserver?.disconnect();
|
|
229
|
+
this._resizeObserver = undefined;
|
|
230
|
+
super.disconnectedCallback();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
override firstUpdated() {
|
|
234
|
+
this._resizeObserver?.observe(this._input);
|
|
235
|
+
this._resizeTextarea();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private _handleInput(event: Event) {
|
|
239
|
+
const input = event.currentTarget as HTMLTextAreaElement;
|
|
240
|
+
this.value = input.value;
|
|
241
|
+
this._internals?.setFormValue?.(this.value);
|
|
242
|
+
this._resizeTextarea();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private _resizeTextarea() {
|
|
246
|
+
const input = this._input;
|
|
247
|
+
if (!input) return;
|
|
248
|
+
|
|
249
|
+
if (!this.autosize) {
|
|
250
|
+
input.style.removeProperty("block-size");
|
|
251
|
+
input.style.removeProperty("overflow-y");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const styles = getComputedStyle(input);
|
|
256
|
+
const lineHeight = Number.parseFloat(styles.lineHeight) || 24;
|
|
257
|
+
const padding =
|
|
258
|
+
Number.parseFloat(styles.paddingTop) +
|
|
259
|
+
Number.parseFloat(styles.paddingBottom);
|
|
260
|
+
const border =
|
|
261
|
+
Number.parseFloat(styles.borderTopWidth) +
|
|
262
|
+
Number.parseFloat(styles.borderBottomWidth);
|
|
263
|
+
const minimum = lineHeight * Math.max(1, this.rows) + padding + border;
|
|
264
|
+
const maximum =
|
|
265
|
+
this.maxRows > 0
|
|
266
|
+
? lineHeight * Math.max(this.rows, this.maxRows) + padding + border
|
|
267
|
+
: Number.POSITIVE_INFINITY;
|
|
268
|
+
|
|
269
|
+
input.style.blockSize = "auto";
|
|
270
|
+
const height = Math.min(
|
|
271
|
+
maximum,
|
|
272
|
+
Math.max(minimum, input.scrollHeight + border),
|
|
273
|
+
);
|
|
274
|
+
input.style.blockSize = `${height}px`;
|
|
275
|
+
input.style.overflowY =
|
|
276
|
+
input.scrollHeight + border > maximum ? "auto" : "hidden";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
override updated(changed: Map<string, unknown>) {
|
|
280
|
+
if (this._input) {
|
|
281
|
+
if (changed.has("value")) {
|
|
282
|
+
this._input.value = this.value;
|
|
283
|
+
this._internals?.setFormValue?.(this.value);
|
|
284
|
+
}
|
|
285
|
+
if (changed.has("disabled")) this._input.disabled = this.disabled;
|
|
286
|
+
if (changed.has("maxlength") && this.maxlength != null) {
|
|
287
|
+
this._input.maxLength = this.maxlength;
|
|
288
|
+
}
|
|
289
|
+
if (
|
|
290
|
+
changed.has("value") ||
|
|
291
|
+
changed.has("autosize") ||
|
|
292
|
+
changed.has("rows") ||
|
|
293
|
+
changed.has("maxRows")
|
|
294
|
+
)
|
|
295
|
+
this._resizeTextarea();
|
|
296
|
+
}
|
|
297
|
+
this._syncValidity();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Copia la validez del `<textarea>` interno al host.
|
|
302
|
+
*
|
|
303
|
+
* El navegador valida mirando los controles asociados al formulario, y el
|
|
304
|
+
* que participa es el host, no el control del Shadow DOM: sin este puente
|
|
305
|
+
* `required` se pintaría pero no detendría el envío.
|
|
306
|
+
*/
|
|
307
|
+
/**
|
|
308
|
+
* API de validación equivalente a la de un control nativo.
|
|
309
|
+
*
|
|
310
|
+
* Los elementos form-associated participan en la validación del formulario,
|
|
311
|
+
* pero no reciben estos miembros automáticamente: hay que exponerlos para que
|
|
312
|
+
* un `moni-textarea` se pueda interrogar igual que un `<input>`.
|
|
313
|
+
*/
|
|
314
|
+
get validity(): ValidityState {
|
|
315
|
+
return this._internals.validity;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
get validationMessage(): string {
|
|
319
|
+
return this._internals.validationMessage;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
checkValidity(): boolean {
|
|
323
|
+
return this._internals.checkValidity();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
reportValidity(): boolean {
|
|
327
|
+
return this._internals.reportValidity();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private _syncValidity() {
|
|
331
|
+
if (!this._input || !this._internals?.setValidity) return;
|
|
332
|
+
this._internals.setValidity(
|
|
333
|
+
this._input.validity,
|
|
334
|
+
this._input.validationMessage,
|
|
335
|
+
this._input,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
static override styles = [
|
|
340
|
+
sharedStyles,
|
|
341
|
+
fieldStyles,
|
|
342
|
+
css`
|
|
343
|
+
:host([autosize]) textarea {
|
|
344
|
+
resize: none;
|
|
345
|
+
}
|
|
346
|
+
`,
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Renderiza el campo del área de texto con etiqueta, iconos de prefijo/sufijo y contador de caracteres.
|
|
351
|
+
*
|
|
352
|
+
* **Composición de `fieldClasses`:**
|
|
353
|
+
* Sigue la convención de nomenclatura field-styles. `prefix` se añade
|
|
354
|
+
* cuando el texto de `icon` o `prefix` está presente (desplaza el inicio en línea de la etiqueta).
|
|
355
|
+
* `suffix` se añade cuando `trailingIcon`, el texto `suffix` o `loading` es true.
|
|
356
|
+
*
|
|
357
|
+
* **Contador de caracteres:**
|
|
358
|
+
* `showCounter` es true cuando `maxlength > 0` y `noCounter` no está configurado.
|
|
359
|
+
* El contador muestra `${this.value.length} / ${this.maxlength}` y puede volverse rojo
|
|
360
|
+
* (vía CSS `invalid`) dependiendo de la validación.
|
|
361
|
+
*/
|
|
362
|
+
override render() {
|
|
363
|
+
const hasLeading = Boolean(this.icon) || Boolean(this.prefix);
|
|
364
|
+
const hasTrailing =
|
|
365
|
+
Boolean(this.trailingIcon) || Boolean(this.suffix) || this.loading;
|
|
366
|
+
const isActive = Boolean(this.value) || Boolean(this.placeholder);
|
|
367
|
+
const showCounter =
|
|
368
|
+
!this.noCounter && this.maxlength != null && this.maxlength > 0;
|
|
369
|
+
const fieldClasses = {
|
|
370
|
+
field: true,
|
|
371
|
+
label: Boolean(this.label),
|
|
372
|
+
fill: this.variant === "filled",
|
|
373
|
+
border: this.variant === "outlined",
|
|
374
|
+
small: this.size === "small",
|
|
375
|
+
large: this.size === "large",
|
|
376
|
+
extra: this.size === "extra",
|
|
377
|
+
prefix: hasLeading,
|
|
378
|
+
suffix: hasTrailing,
|
|
379
|
+
invalid: this.error,
|
|
380
|
+
round: this.shape === "round",
|
|
381
|
+
"small-round": this.shape === "small-round",
|
|
382
|
+
square: this.shape === "no-round",
|
|
383
|
+
};
|
|
384
|
+
const placeholder = this.placeholder || (this.label ? " " : "");
|
|
385
|
+
|
|
386
|
+
const leading = this.icon
|
|
387
|
+
? html`<i class="leading-icon" part="leading-icon"
|
|
388
|
+
><moni-icon name="${this.icon}"></moni-icon
|
|
389
|
+
></i>`
|
|
390
|
+
: this.prefix
|
|
391
|
+
? html`<span class="leading-icon" part="prefix">${this.prefix}</span>`
|
|
392
|
+
: nothing;
|
|
393
|
+
|
|
394
|
+
const trailing = this.loading
|
|
395
|
+
? html`<i class="trailing-icon" part="trailing-icon"
|
|
396
|
+
><moni-progress
|
|
397
|
+
variant="circular"
|
|
398
|
+
indeterminate
|
|
399
|
+
size="small"
|
|
400
|
+
style="inline-size: 1.25rem; block-size: 1.25rem; color: currentColor;"
|
|
401
|
+
></moni-progress
|
|
402
|
+
></i>`
|
|
403
|
+
: this.trailingIcon
|
|
404
|
+
? html`<i class="trailing-icon" part="trailing-icon"
|
|
405
|
+
><moni-icon name="${this.trailingIcon}"></moni-icon
|
|
406
|
+
></i>`
|
|
407
|
+
: this.suffix
|
|
408
|
+
? html`<span class="trailing-icon" part="suffix"
|
|
409
|
+
>${this.suffix}</span
|
|
410
|
+
>`
|
|
411
|
+
: nothing;
|
|
412
|
+
|
|
413
|
+
// Especificación M3: texto de soporte a la izquierda, contador de caracteres a la derecha.
|
|
414
|
+
const counter = showCounter
|
|
415
|
+
? html`<output part="counter" class="counter"
|
|
416
|
+
>${this.value.length} / ${this.maxlength}</output
|
|
417
|
+
>`
|
|
418
|
+
: nothing;
|
|
419
|
+
const helperText = this.error
|
|
420
|
+
? html`<output part="helper" class="invalid"
|
|
421
|
+
>${this.errorText || this.helper}</output
|
|
422
|
+
>`
|
|
423
|
+
: this.helper
|
|
424
|
+
? html`<output part="helper">${this.helper}</output>`
|
|
425
|
+
: nothing;
|
|
426
|
+
const hasFooter = helperText || counter;
|
|
427
|
+
|
|
428
|
+
return html`<div class=${classMap(fieldClasses)} part="field">
|
|
429
|
+
${leading}
|
|
430
|
+
<textarea
|
|
431
|
+
id="input"
|
|
432
|
+
part="input"
|
|
433
|
+
rows=${this.rows}
|
|
434
|
+
maxlength=${ifDefined(this.maxlength ?? undefined)}
|
|
435
|
+
minlength=${ifDefined(this.minlength ?? undefined)}
|
|
436
|
+
placeholder=${placeholder}
|
|
437
|
+
?disabled=${this.disabled}
|
|
438
|
+
?required=${this.required}
|
|
439
|
+
?readonly=${this.readonly}
|
|
440
|
+
autocomplete=${ifDefined(this.autocomplete || undefined)}
|
|
441
|
+
.value=${this.value}
|
|
442
|
+
name=${ifDefined(this.name || undefined)}
|
|
443
|
+
class=${isActive ? "active" : ""}
|
|
444
|
+
@input=${this._handleInput}
|
|
445
|
+
></textarea>
|
|
446
|
+
${
|
|
447
|
+
this.label
|
|
448
|
+
? html`<label
|
|
449
|
+
for="input"
|
|
450
|
+
part="label"
|
|
451
|
+
class=${classMap({ active: isActive })}
|
|
452
|
+
>${this.label}</label
|
|
453
|
+
>`
|
|
454
|
+
: nothing
|
|
455
|
+
}
|
|
456
|
+
${trailing}
|
|
457
|
+
${
|
|
458
|
+
hasFooter
|
|
459
|
+
? html`<div class="footer" part="footer">
|
|
460
|
+
${helperText}
|
|
461
|
+
<div class="spacer"></div>
|
|
462
|
+
${counter}
|
|
463
|
+
</div>`
|
|
464
|
+
: nothing
|
|
465
|
+
}
|
|
466
|
+
</div>`;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
declare global {
|
|
471
|
+
interface HTMLElementTagNameMap {
|
|
472
|
+
"moni-textarea": MoniTextarea;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export default MoniTextarea;
|