@moni-labs/moni-ui 0.3.0 → 0.3.2
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 +5230 -1007
- package/dist/components/_base/moni-element.d.ts +16 -9
- package/dist/components/_base/moni-element.d.ts.map +1 -1
- package/dist/components/_base/moni-element.js +63 -9
- package/dist/components/index.js +2 -2
- package/dist/components/loading-shapes.d.ts +19 -0
- package/dist/components/loading-shapes.d.ts.map +1 -1
- package/dist/components/loading-shapes.js +19 -0
- package/dist/components/moni-app-bar.d.ts +72 -62
- package/dist/components/moni-app-bar.d.ts.map +1 -1
- package/dist/components/moni-app-bar.js +72 -62
- package/dist/components/moni-badge.d.ts +64 -65
- package/dist/components/moni-badge.d.ts.map +1 -1
- package/dist/components/moni-badge.js +64 -66
- package/dist/components/moni-bottom-sheet.d.ts +129 -61
- package/dist/components/moni-bottom-sheet.d.ts.map +1 -1
- package/dist/components/moni-bottom-sheet.js +217 -131
- package/dist/components/moni-button-group.d.ts +107 -29
- package/dist/components/moni-button-group.d.ts.map +1 -1
- package/dist/components/moni-button-group.js +107 -29
- package/dist/components/moni-button-segment.d.ts +53 -12
- package/dist/components/moni-button-segment.d.ts.map +1 -1
- package/dist/components/moni-button-segment.js +53 -12
- package/dist/components/moni-button.d.ts +116 -38
- package/dist/components/moni-button.d.ts.map +1 -1
- package/dist/components/moni-button.js +128 -38
- package/dist/components/moni-card.d.ts +68 -50
- package/dist/components/moni-card.d.ts.map +1 -1
- package/dist/components/moni-card.js +68 -50
- package/dist/components/moni-carousel.d.ts +484 -53
- package/dist/components/moni-carousel.d.ts.map +1 -1
- package/dist/components/moni-carousel.js +1024 -245
- package/dist/components/moni-checkbox.d.ts +49 -46
- package/dist/components/moni-checkbox.d.ts.map +1 -1
- package/dist/components/moni-checkbox.js +55 -48
- package/dist/components/moni-chip.d.ts +100 -33
- package/dist/components/moni-chip.d.ts.map +1 -1
- package/dist/components/moni-chip.js +104 -34
- package/dist/components/moni-color-field.d.ts +99 -24
- package/dist/components/moni-color-field.d.ts.map +1 -1
- package/dist/components/moni-color-field.js +99 -24
- package/dist/components/moni-context-menu.d.ts +72 -26
- package/dist/components/moni-context-menu.d.ts.map +1 -1
- package/dist/components/moni-context-menu.js +76 -30
- package/dist/components/moni-dialog.d.ts +84 -59
- package/dist/components/moni-dialog.d.ts.map +1 -1
- package/dist/components/moni-dialog.js +105 -58
- package/dist/components/moni-divider.d.ts +39 -27
- package/dist/components/moni-divider.d.ts.map +1 -1
- package/dist/components/moni-divider.js +42 -30
- package/dist/components/moni-expansion.d.ts +32 -22
- package/dist/components/moni-expansion.d.ts.map +1 -1
- package/dist/components/moni-expansion.js +32 -22
- package/dist/components/moni-fab-menu.d.ts +61 -23
- package/dist/components/moni-fab-menu.d.ts.map +1 -1
- package/dist/components/moni-fab-menu.js +69 -31
- package/dist/components/moni-fab.d.ts +86 -28
- package/dist/components/moni-fab.d.ts.map +1 -1
- package/dist/components/moni-fab.js +92 -34
- package/dist/components/moni-file-field.d.ts +118 -30
- package/dist/components/moni-file-field.d.ts.map +1 -1
- package/dist/components/moni-file-field.js +118 -30
- package/dist/components/moni-icon.d.ts +40 -35
- package/dist/components/moni-icon.d.ts.map +1 -1
- package/dist/components/moni-icon.js +43 -38
- package/dist/components/moni-list-item.d.ts +46 -33
- package/dist/components/moni-list-item.d.ts.map +1 -1
- package/dist/components/moni-list-item.js +61 -41
- package/dist/components/moni-list.d.ts +30 -20
- package/dist/components/moni-list.d.ts.map +1 -1
- package/dist/components/moni-list.js +36 -21
- package/dist/components/moni-loading-indicator.d.ts +54 -22
- package/dist/components/moni-loading-indicator.d.ts.map +1 -1
- package/dist/components/moni-loading-indicator.js +54 -22
- package/dist/components/moni-menu-item.d.ts +23 -16
- package/dist/components/moni-menu-item.d.ts.map +1 -1
- package/dist/components/moni-menu-item.js +23 -16
- package/dist/components/moni-menu.d.ts +81 -28
- package/dist/components/moni-menu.d.ts.map +1 -1
- package/dist/components/moni-menu.js +81 -30
- package/dist/components/moni-morph-modal.d.ts +141 -0
- package/dist/components/moni-morph-modal.d.ts.map +1 -1
- package/dist/components/moni-morph-modal.js +295 -58
- package/dist/components/moni-nav-item.d.ts +59 -29
- package/dist/components/moni-nav-item.d.ts.map +1 -1
- package/dist/components/moni-nav-item.js +59 -29
- package/dist/components/moni-nav.d.ts +94 -35
- package/dist/components/moni-nav.d.ts.map +1 -1
- package/dist/components/moni-nav.js +94 -35
- package/dist/components/moni-progress.d.ts +99 -55
- package/dist/components/moni-progress.d.ts.map +1 -1
- package/dist/components/moni-progress.js +99 -55
- package/dist/components/moni-radio.d.ts +62 -50
- package/dist/components/moni-radio.d.ts.map +1 -1
- package/dist/components/moni-radio.js +65 -52
- package/dist/components/moni-ripple.d.ts +74 -59
- package/dist/components/moni-ripple.d.ts.map +1 -1
- package/dist/components/moni-ripple.js +74 -59
- package/dist/components/moni-segmented-button.d.ts +91 -16
- package/dist/components/moni-segmented-button.d.ts.map +1 -1
- package/dist/components/moni-segmented-button.js +94 -17
- package/dist/components/moni-select-option.d.ts +30 -23
- package/dist/components/moni-select-option.d.ts.map +1 -1
- package/dist/components/moni-select-option.js +30 -23
- package/dist/components/moni-select.d.ts +300 -35
- package/dist/components/moni-select.d.ts.map +1 -1
- package/dist/components/moni-select.js +323 -39
- package/dist/components/moni-side-sheet.d.ts +94 -32
- package/dist/components/moni-side-sheet.d.ts.map +1 -1
- package/dist/components/moni-side-sheet.js +94 -32
- package/dist/components/moni-slider.d.ts +152 -41
- package/dist/components/moni-slider.d.ts.map +1 -1
- package/dist/components/moni-slider.js +154 -42
- package/dist/components/moni-snackbar.d.ts +53 -43
- package/dist/components/moni-snackbar.d.ts.map +1 -1
- package/dist/components/moni-snackbar.js +53 -43
- package/dist/components/moni-split-button.d.ts +63 -18
- package/dist/components/moni-split-button.d.ts.map +1 -1
- package/dist/components/moni-split-button.js +63 -18
- package/dist/components/moni-step.d.ts +36 -24
- package/dist/components/moni-step.d.ts.map +1 -1
- package/dist/components/moni-step.js +41 -29
- package/dist/components/moni-stepper.d.ts +64 -27
- package/dist/components/moni-stepper.d.ts.map +1 -1
- package/dist/components/moni-stepper.js +72 -34
- package/dist/components/moni-switch.d.ts +57 -46
- package/dist/components/moni-switch.d.ts.map +1 -1
- package/dist/components/moni-switch.js +59 -47
- package/dist/components/moni-tab.d.ts +26 -18
- package/dist/components/moni-tab.d.ts.map +1 -1
- package/dist/components/moni-tab.js +29 -21
- package/dist/components/moni-tabs.d.ts +34 -28
- package/dist/components/moni-tabs.d.ts.map +1 -1
- package/dist/components/moni-tabs.js +34 -28
- package/dist/components/moni-text-field.d.ts +139 -29
- package/dist/components/moni-text-field.d.ts.map +1 -1
- package/dist/components/moni-text-field.js +157 -31
- package/dist/components/moni-textarea.d.ts +127 -29
- package/dist/components/moni-textarea.d.ts.map +1 -1
- package/dist/components/moni-textarea.js +131 -32
- package/dist/components/moni-time-picker.d.ts +114 -24
- package/dist/components/moni-time-picker.d.ts.map +1 -1
- package/dist/components/moni-time-picker.js +127 -33
- package/dist/components/moni-toolbar.d.ts +37 -23
- package/dist/components/moni-toolbar.d.ts.map +1 -1
- package/dist/components/moni-toolbar.js +37 -23
- package/dist/components/moni-tooltip.d.ts +100 -36
- package/dist/components/moni-tooltip.d.ts.map +1 -1
- package/dist/components/moni-tooltip.js +104 -40
- package/dist/components/moni-typography.d.ts +68 -30
- package/dist/components/moni-typography.d.ts.map +1 -1
- package/dist/components/moni-typography.js +70 -32
- package/dist/styles/tokens.css +7 -7
- package/dist/types/events.d.ts +17 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +1 -0
- package/dist/utils/event-emitter.d.ts +13 -0
- package/dist/utils/event-emitter.d.ts.map +1 -0
- package/dist/utils/event-emitter.js +13 -0
- package/package.json +2 -1
- package/src/components/_base/moni-element.ts +73 -9
- package/src/components/index.ts +2 -2
- package/src/components/loading-shapes.ts +19 -0
- package/src/components/moni-app-bar.test.ts +10 -10
- package/src/components/moni-app-bar.ts +72 -62
- package/src/components/moni-badge.ts +64 -66
- package/src/components/moni-bottom-sheet.test.ts +39 -45
- package/src/components/moni-bottom-sheet.ts +222 -131
- package/src/components/moni-button-group.test.ts +14 -14
- package/src/components/moni-button-group.ts +107 -29
- package/src/components/moni-button-segment.ts +53 -12
- package/src/components/moni-button.test.ts +28 -26
- package/src/components/moni-button.ts +139 -39
- package/src/components/moni-card.test.ts +6 -6
- package/src/components/moni-card.ts +70 -50
- package/src/components/moni-carousel.test.ts +12 -12
- package/src/components/moni-carousel.ts +1129 -269
- package/src/components/moni-checkbox.test.ts +7 -7
- package/src/components/moni-checkbox.ts +62 -48
- package/src/components/moni-chip.test.ts +17 -17
- package/src/components/moni-chip.ts +112 -34
- package/src/components/moni-color-field.test.ts +5 -5
- package/src/components/moni-color-field.ts +108 -24
- package/src/components/moni-context-menu.test.ts +11 -11
- package/src/components/moni-context-menu.ts +76 -30
- package/src/components/moni-dialog.ts +108 -59
- package/src/components/moni-divider.test.ts +4 -4
- package/src/components/moni-divider.ts +42 -30
- package/src/components/moni-expansion.ts +32 -22
- package/src/components/moni-fab-menu.test.ts +11 -11
- package/src/components/moni-fab-menu.ts +69 -31
- package/src/components/moni-fab.test.ts +16 -16
- package/src/components/moni-fab.ts +100 -34
- package/src/components/moni-file-field.test.ts +8 -8
- package/src/components/moni-file-field.ts +132 -30
- package/src/components/moni-icon.test.ts +11 -11
- package/src/components/moni-icon.ts +43 -38
- package/src/components/moni-list-item.test.ts +10 -10
- package/src/components/moni-list-item.ts +61 -41
- package/src/components/moni-list.ts +37 -21
- package/src/components/moni-loading-indicator.test.ts +3 -3
- package/src/components/moni-loading-indicator.ts +54 -22
- package/src/components/moni-menu-item.ts +23 -16
- package/src/components/moni-menu.test.ts +7 -7
- package/src/components/moni-menu.ts +87 -30
- package/src/components/moni-morph-modal.test.ts +18 -17
- package/src/components/moni-morph-modal.ts +327 -60
- package/src/components/moni-nav-item.ts +59 -29
- package/src/components/moni-nav.test.ts +11 -11
- package/src/components/moni-nav.ts +94 -35
- package/src/components/moni-progress.test.ts +9 -9
- package/src/components/moni-progress.ts +100 -56
- package/src/components/moni-radio.test.ts +8 -8
- package/src/components/moni-radio.ts +71 -51
- package/src/components/moni-ripple.ts +74 -59
- package/src/components/moni-segmented-button.test.ts +4 -4
- package/src/components/moni-segmented-button.ts +94 -17
- package/src/components/moni-select-option.ts +30 -23
- package/src/components/moni-select.test.ts +11 -11
- package/src/components/moni-select.ts +361 -50
- package/src/components/moni-side-sheet.test.ts +7 -7
- package/src/components/moni-side-sheet.ts +94 -32
- package/src/components/moni-slider.test.ts +7 -7
- package/src/components/moni-slider.ts +165 -42
- package/src/components/moni-snackbar.test.ts +8 -8
- package/src/components/moni-snackbar.ts +53 -43
- package/src/components/moni-split-button.ts +63 -18
- package/src/components/moni-step.test.ts +7 -7
- package/src/components/moni-step.ts +41 -29
- package/src/components/moni-stepper.test.ts +7 -7
- package/src/components/moni-stepper.ts +72 -34
- package/src/components/moni-switch.test.ts +12 -12
- package/src/components/moni-switch.ts +66 -47
- package/src/components/moni-tab.test.ts +5 -5
- package/src/components/moni-tab.ts +29 -21
- package/src/components/moni-tabs.ts +34 -28
- package/src/components/moni-text-field.test.ts +13 -13
- package/src/components/moni-text-field.ts +181 -30
- package/src/components/moni-textarea.test.ts +17 -17
- package/src/components/moni-textarea.ts +155 -31
- package/src/components/moni-time-picker.test.ts +5 -5
- package/src/components/moni-time-picker.ts +128 -33
- package/src/components/moni-toolbar.test.ts +8 -8
- package/src/components/moni-toolbar.ts +37 -23
- package/src/components/moni-tooltip.test.ts +15 -15
- package/src/components/moni-tooltip.ts +107 -40
- package/src/components/moni-typography.test.ts +10 -10
- package/src/components/moni-typography.ts +73 -32
- package/src/styles/tokens.css +7 -7
- package/src/types/events.ts +17 -0
- package/src/utils/event-emitter.ts +26 -0
|
@@ -13,41 +13,41 @@ import './moni-icon.js';
|
|
|
13
13
|
import type { MoniFab } from './moni-fab.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Material Design 3 FAB Menu
|
|
16
|
+
* Componente Material Design 3 FAB Menu (Menú FAB).
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* `<menu>`
|
|
20
|
-
*
|
|
18
|
+
* Un contenedor especializado que combina un disparador principal `<moni-fab>` con un
|
|
19
|
+
* `<menu>` de FABs secundarios. Permite ocultar múltiples acciones
|
|
20
|
+
* detrás de un solo Botón de Acción Flotante primario, reduciendo el desorden en la pantalla.
|
|
21
21
|
*
|
|
22
|
-
* **
|
|
22
|
+
* **Referencia de la especificación M3:** `m3-docs/components/floating-action-buttons/specs.md` (Menús FAB)
|
|
23
23
|
*
|
|
24
|
-
* **
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* **Disparador y animación:**
|
|
25
|
+
* El componente enlaza el evento `click` del FAB disparador principal para alternar su
|
|
26
|
+
* estado interno `open`. Cuando `open=true`, los FABs secundarios anidados se escalan
|
|
27
|
+
* y aparecen mediante transiciones CSS. Los consumidores también pueden controlar el estado `open`
|
|
28
|
+
* programáticamente estableciendo el atributo.
|
|
29
29
|
*
|
|
30
|
-
* **
|
|
31
|
-
* -
|
|
32
|
-
*
|
|
33
|
-
* -
|
|
34
|
-
*
|
|
35
|
-
* -
|
|
36
|
-
*
|
|
30
|
+
* **Gestión del foco (Accesibilidad):**
|
|
31
|
+
* - Cuando el menú se abre, el foco se mueve automáticamente al primer elemento secundario
|
|
32
|
+
* enfocable (o permanece en el disparador si está vacío).
|
|
33
|
+
* - Mientras está abierto, la tecla `Tab` cicla el foco estrictamente dentro de los elementos del menú para
|
|
34
|
+
* evitar que el foco del teclado escape (trampa de foco).
|
|
35
|
+
* - Presionar `Escape` o hacer clic en cualquier lugar fuera del menú lo cierra y
|
|
36
|
+
* devuelve el foco al FAB disparador primario.
|
|
37
37
|
*
|
|
38
38
|
* @example
|
|
39
39
|
* ```html
|
|
40
|
-
* <!--
|
|
40
|
+
* <!-- Menú FAB en la parte inferior derecha que se abre hacia arriba -->
|
|
41
41
|
* <moni-fab-menu icon="add" color="tertiary" direction="up">
|
|
42
|
-
* <moni-fab size="small" icon="edit" label="
|
|
43
|
-
* <moni-fab size="small" icon="photo_camera" label="
|
|
42
|
+
* <moni-fab size="small" icon="edit" label="Borrador"></moni-fab>
|
|
43
|
+
* <moni-fab size="small" icon="photo_camera" label="Cámara"></moni-fab>
|
|
44
44
|
* </moni-fab-menu>
|
|
45
45
|
* ```
|
|
46
46
|
*
|
|
47
|
-
* @slot default -
|
|
47
|
+
* @slot default - Los elementos `<moni-fab>` secundarios que aparecen cuando está abierto.
|
|
48
48
|
*
|
|
49
|
-
* @csspart trigger -
|
|
50
|
-
* @csspart menu -
|
|
49
|
+
* @csspart trigger - El elemento disparador `<moni-fab>` primario.
|
|
50
|
+
* @csspart menu - El contenedor `<menu>` que contiene los elementos secundarios.
|
|
51
51
|
*/
|
|
52
52
|
@customElement('moni-fab-menu')
|
|
53
53
|
export class MoniFabMenu extends MoniElement {
|
|
@@ -72,17 +72,35 @@ export class MoniFabMenu extends MoniElement {
|
|
|
72
72
|
@query('.fab-menu') private _menu!: HTMLElement;
|
|
73
73
|
|
|
74
74
|
private _previouslyFocused: HTMLElement | null = null;
|
|
75
|
+
/**
|
|
76
|
+
* Wrapper estático para el manejador de clics globales.
|
|
77
|
+
* Conserva el contexto léxico `this` al ser inyectado/removido en `document`.
|
|
78
|
+
*/
|
|
75
79
|
private _onDocClick = (e: MouseEvent) => this._handleDocClick(e);
|
|
80
|
+
/**
|
|
81
|
+
* Wrapper estático para el manejador de teclado global.
|
|
82
|
+
* Facilita la limpieza del listener de Escape/Navegación al desmontar el componente.
|
|
83
|
+
*/
|
|
76
84
|
private _onDocKeydown = (e: KeyboardEvent) => this._handleDocKeydown(e);
|
|
77
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Hook del ciclo de vida (Lit). Se ejecuta una sola vez tras el primer renderizado.
|
|
88
|
+
* Espera a que el Shadow DOM se materialice por completo y enlaza el evento nativo
|
|
89
|
+
* `click` directamente sobre el FAB (Floating Action Button) principal que acciona el menú.
|
|
90
|
+
*/
|
|
78
91
|
override async firstUpdated() {
|
|
79
|
-
//
|
|
92
|
+
// Esperar a que todos los hijos terminen de renderizarse antes de enlazar los detectores
|
|
80
93
|
await this.updateComplete;
|
|
81
94
|
if (this._trigger) {
|
|
82
95
|
this._trigger.addEventListener('click', this._onTriggerClick);
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Limpieza rigurosa de memoria (Garbage Collection).
|
|
101
|
+
* Remueve absolutamente todos los listeners asíncronos (document y trigger local)
|
|
102
|
+
* para evitar colisiones de eventos fantasma y pérdidas de rendimiento (memory leaks).
|
|
103
|
+
*/
|
|
86
104
|
override disconnectedCallback() {
|
|
87
105
|
super.disconnectedCallback();
|
|
88
106
|
this._trigger?.removeEventListener('click', this._onTriggerClick);
|
|
@@ -97,28 +115,32 @@ export class MoniFabMenu extends MoniElement {
|
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Alterna explícitamente el estado `open` del menú.
|
|
120
|
+
* Se dispara exclusivamente al interactuar física o focalmente con el FAB principal.
|
|
121
|
+
*/
|
|
100
122
|
private _onTriggerClick = () => {
|
|
101
123
|
this.open = !this.open;
|
|
102
124
|
};
|
|
103
125
|
|
|
104
126
|
private _syncOpenState(): void {
|
|
105
127
|
if (this.open) {
|
|
106
|
-
//
|
|
128
|
+
// Guarda el foco actual para poder restaurarlo al cerrar.
|
|
107
129
|
this._previouslyFocused = (this.getRootNode() as unknown as DocumentOrShadowRoot)
|
|
108
130
|
.activeElement as HTMLElement | null;
|
|
109
|
-
//
|
|
110
|
-
//
|
|
131
|
+
// Mueve el foco al primer elemento enfocable en el menú, o al
|
|
132
|
+
// disparador como respaldo (según el patrón de menú WAI-ARIA).
|
|
111
133
|
const first = this._firstFocusableMenuItem();
|
|
112
134
|
if (first) {
|
|
113
135
|
first.focus();
|
|
114
136
|
} else {
|
|
115
137
|
this._trigger?.focus();
|
|
116
138
|
}
|
|
117
|
-
//
|
|
139
|
+
// Instala detectores en el documento para clic fuera y Escape.
|
|
118
140
|
document.addEventListener('click', this._onDocClick, true);
|
|
119
141
|
document.addEventListener('keydown', this._onDocKeydown, true);
|
|
120
142
|
} else {
|
|
121
|
-
//
|
|
143
|
+
// Restaura el foco al disparador (o al elemento previamente enfocado).
|
|
122
144
|
const restore = this._previouslyFocused ?? this._trigger;
|
|
123
145
|
restore?.focus();
|
|
124
146
|
this._previouslyFocused = null;
|
|
@@ -206,7 +228,7 @@ export class MoniFabMenu extends MoniElement {
|
|
|
206
228
|
transform: scale(1);
|
|
207
229
|
}
|
|
208
230
|
|
|
209
|
-
/*
|
|
231
|
+
/* Colocación direccional relativa al disparador */
|
|
210
232
|
:host([direction='up']) .fab-menu {
|
|
211
233
|
inset: auto auto calc(100% + 0.5rem) 0;
|
|
212
234
|
flex-direction: column-reverse;
|
|
@@ -231,9 +253,25 @@ export class MoniFabMenu extends MoniElement {
|
|
|
231
253
|
`
|
|
232
254
|
];
|
|
233
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Renderiza el speed-dial FAB como un FAB disparador + menú colapsable de FABs de acción.
|
|
258
|
+
*
|
|
259
|
+
* **Patrón Speed-dial:**
|
|
260
|
+
* El FAB disparador siempre es visible. Cuando `open=true`, los elementos de acción en
|
|
261
|
+
* el slot por defecto se revelan con una animación en cascada. Los elementos se colapsan
|
|
262
|
+
* de nuevo con una cascada invertida.
|
|
263
|
+
*
|
|
264
|
+
* **Dirección de cascada:**
|
|
265
|
+
* Cuando `placement='bottom'`, los elementos se expanden hacia arriba (índice 0 en la parte inferior).
|
|
266
|
+
* Cuando `placement='top'`, los elementos se expanden hacia abajo.
|
|
267
|
+
*
|
|
268
|
+
* **`aria-expanded`:**
|
|
269
|
+
* Se establece en el botón disparador para comunicar el estado abierto/cerrado a los lectores de pantalla.
|
|
270
|
+
* Cada FAB de acción en el slot lleva `aria-label` que el consumidor debe proporcionar.
|
|
271
|
+
*/
|
|
234
272
|
override render() {
|
|
235
273
|
return html`<div class="wrap">
|
|
236
|
-
<!-- div role=menu
|
|
274
|
+
<!-- div role=menu permite cualquier contenido en el slot (no solo <li>) -->
|
|
237
275
|
<div class="fab-menu" role="menu" part="menu">
|
|
238
276
|
<slot></slot>
|
|
239
277
|
</div>
|
|
@@ -14,7 +14,7 @@ describe('moni-fab', () => {
|
|
|
14
14
|
el.remove();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it('
|
|
17
|
+
it('renderiza un <button> con el icono por defecto "add"', async () => {
|
|
18
18
|
await el.updateComplete;
|
|
19
19
|
const button = el.shadowRoot?.querySelector('button');
|
|
20
20
|
const icon = el.shadowRoot?.querySelector('moni-icon');
|
|
@@ -22,21 +22,21 @@ describe('moni-fab', () => {
|
|
|
22
22
|
expect(icon?.getAttribute('name')).toBe('add');
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it('
|
|
25
|
+
it('usa el nombre del icono proporcionado', async () => {
|
|
26
26
|
el.icon = 'edit';
|
|
27
27
|
await el.updateComplete;
|
|
28
28
|
const icon = el.shadowRoot?.querySelector('moni-icon');
|
|
29
29
|
expect(icon?.getAttribute('name')).toBe('edit');
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
it('
|
|
32
|
+
it('renderiza el texto del label cuando se establece (visible por defecto, no requiere extended)', async () => {
|
|
33
33
|
el.label = 'Create';
|
|
34
34
|
await el.updateComplete;
|
|
35
35
|
const label = el.shadowRoot?.querySelector('.label');
|
|
36
36
|
expect(label?.textContent).toBe('Create');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it('
|
|
39
|
+
it('no requiere extended/expanded para que el label sea visible', async () => {
|
|
40
40
|
el.label = 'Send';
|
|
41
41
|
await el.updateComplete;
|
|
42
42
|
expect(el.hasAttribute('extended')).toBe(false);
|
|
@@ -45,20 +45,20 @@ describe('moni-fab', () => {
|
|
|
45
45
|
expect(label?.textContent).toBe('Send');
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
it('
|
|
48
|
+
it('oculta el label visualmente cuando no se establece (span vacío + display:none)', async () => {
|
|
49
49
|
await el.updateComplete;
|
|
50
50
|
const label = el.shadowRoot?.querySelector('.label');
|
|
51
51
|
expect(label?.textContent).toBe('');
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
it('
|
|
54
|
+
it('aplica la clase shape=circle', async () => {
|
|
55
55
|
el.shape = 'circle';
|
|
56
56
|
await el.updateComplete;
|
|
57
57
|
const button = el.shadowRoot?.querySelector('button');
|
|
58
58
|
expect(button?.classList.contains('circle')).toBe(true);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
it('
|
|
61
|
+
it('aplica las clases de color (secondary/tertiary/surface)', async () => {
|
|
62
62
|
el.color = 'secondary';
|
|
63
63
|
await el.updateComplete;
|
|
64
64
|
const button = el.shadowRoot?.querySelector('button');
|
|
@@ -73,7 +73,7 @@ describe('moni-fab', () => {
|
|
|
73
73
|
expect(button?.classList.contains('surface')).toBe(true);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
it('
|
|
76
|
+
it('aplica las clases de tamaño (small/large)', async () => {
|
|
77
77
|
el.size = 'small';
|
|
78
78
|
await el.updateComplete;
|
|
79
79
|
const button = el.shadowRoot?.querySelector('button');
|
|
@@ -84,13 +84,13 @@ describe('moni-fab', () => {
|
|
|
84
84
|
expect(button?.classList.contains('large')).toBe(true);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
it('
|
|
87
|
+
it('refleja el atributo position', async () => {
|
|
88
88
|
el.position = 'top-leading';
|
|
89
89
|
await el.updateComplete;
|
|
90
90
|
expect(el.getAttribute('position')).toBe('top-leading');
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
it('
|
|
93
|
+
it('refleja los atributos extended y expanded', async () => {
|
|
94
94
|
el.extended = true;
|
|
95
95
|
await el.updateComplete;
|
|
96
96
|
expect(el.hasAttribute('extended')).toBe(true);
|
|
@@ -100,7 +100,7 @@ describe('moni-fab', () => {
|
|
|
100
100
|
expect(el.hasAttribute('expanded')).toBe(true);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
it('
|
|
103
|
+
it('reenvía disabled al botón', async () => {
|
|
104
104
|
el.disabled = true;
|
|
105
105
|
await el.updateComplete;
|
|
106
106
|
const button = el.shadowRoot?.querySelector(
|
|
@@ -109,11 +109,11 @@ describe('moni-fab', () => {
|
|
|
109
109
|
expect(button.disabled).toBe(true);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
it('
|
|
112
|
+
it('registra una advertencia de obsolescencia cuando se usa size="small" (M3 Expressive deprecia el FAB pequeño)', async () => {
|
|
113
113
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
114
|
+
// Desmontar y volver a montar para ejecutar de nuevo connectedCallback con el
|
|
115
|
+
// tamaño obsoleto ya establecido. El componente lee el atributo
|
|
116
|
+
// del elemento durante la conexión.
|
|
117
117
|
el.remove();
|
|
118
118
|
el.size = 'small';
|
|
119
119
|
document.body.appendChild(el);
|
|
@@ -122,7 +122,7 @@ describe('moni-fab', () => {
|
|
|
122
122
|
String(c[0]).includes('[moni-ui]') &&
|
|
123
123
|
String(c[0]).includes('size="small"')
|
|
124
124
|
);
|
|
125
|
-
expect(call, '
|
|
125
|
+
expect(call, 'se esperaba que una advertencia de obsolescencia mencionara size="small"').toBeTruthy();
|
|
126
126
|
warnSpy.mockRestore();
|
|
127
127
|
});
|
|
128
128
|
});
|
|
@@ -11,64 +11,116 @@ import { MoniElement, sharedStyles } from './_base/index.js';
|
|
|
11
11
|
import './moni-icon.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Material Design 3 Floating Action Button (FAB)
|
|
14
|
+
* Componente Material Design 3 Floating Action Button (FAB).
|
|
15
15
|
*
|
|
16
|
-
* FABs
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Los FABs representan la acción principal, o más común, en una pantalla. Aparecen
|
|
17
|
+
* frente a todo el contenido de la pantalla, típicamente como una forma circular con un icono en
|
|
18
|
+
* su centro.
|
|
19
19
|
*
|
|
20
|
-
* **
|
|
20
|
+
* **Referencia de la especificación M3:** `m3-docs/components/floating-action-buttons/specs.md`
|
|
21
21
|
*
|
|
22
|
-
* **
|
|
23
|
-
* - `size="small"`
|
|
24
|
-
*
|
|
25
|
-
* - `shape="circle"`
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* - `color="surface"`
|
|
29
|
-
*
|
|
22
|
+
* **Actualizaciones y Deprecaciones de M3 Expressive (Mayo 2025):**
|
|
23
|
+
* - `size="small"` **ya no es recomendado** por M3. Use `medium` (40dp o 56dp)
|
|
24
|
+
* o `large` (96dp). Deprecado en v0.3.1; se eliminará en v1.0.
|
|
25
|
+
* - `shape="circle"` **no es parte de M3**. Los FABs M3 usan una forma `rounded` (redondeada) con un
|
|
26
|
+
* radio de esquina de 16dp (que se transforma a un radio `full` al pasar el cursor/enfocar).
|
|
27
|
+
* Deprecado en v0.3.1; se eliminará en v1.0.
|
|
28
|
+
* - `color="surface"` **ya no es recomendado**. Use `primary`, `secondary`,
|
|
29
|
+
* o `tertiary` para asegurar un mapeo de color adecuado al tema M3.
|
|
30
30
|
*
|
|
31
|
-
* **
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
31
|
+
* **FABs Extendidos:**
|
|
32
|
+
* Cuando `extended` es true, el FAB muestra una etiqueta de texto junto al icono.
|
|
33
|
+
* Los FABs extendidos son más anchos y se usan típicamente cuando la acción necesita texto explícito
|
|
34
|
+
* para ser clara. El atributo `expanded` fuerza al FAB a su ancho extendido completo,
|
|
35
|
+
* útil para animar entre los estados estándar y extendido al desplazarse.
|
|
36
36
|
*
|
|
37
|
-
* **
|
|
38
|
-
*
|
|
39
|
-
* (
|
|
37
|
+
* **Posicionamiento:**
|
|
38
|
+
* El atributo `position` aplica un posicionamiento absoluto/fijo predefinido
|
|
39
|
+
* (ej. `bottom-trailing`), encajando el FAB en las esquinas estándar de la pantalla.
|
|
40
40
|
*
|
|
41
41
|
* @example
|
|
42
42
|
* ```html
|
|
43
|
-
* <!--
|
|
43
|
+
* <!-- FAB primario estándar -->
|
|
44
44
|
* <moni-fab icon="edit"></moni-fab>
|
|
45
45
|
*
|
|
46
|
-
* <!--
|
|
47
|
-
* <moni-fab color="tertiary" extended icon="add" label="
|
|
46
|
+
* <!-- FAB terciario extendido -->
|
|
47
|
+
* <moni-fab color="tertiary" extended icon="add" label="Nueva tarea"></moni-fab>
|
|
48
48
|
*
|
|
49
|
-
* <!--
|
|
49
|
+
* <!-- FAB posicionado -->
|
|
50
50
|
* <moni-fab position="bottom-trailing" icon="navigation"></moni-fab>
|
|
51
51
|
* ```
|
|
52
52
|
*
|
|
53
|
-
* @csspart button -
|
|
54
|
-
* @csspart icon -
|
|
55
|
-
* @csspart label -
|
|
53
|
+
* @csspart button - El elemento `<button>` interno.
|
|
54
|
+
* @csspart icon - El contenedor para el icono.
|
|
55
|
+
* @csspart label - La etiqueta de texto (solo visible cuando está extendido).
|
|
56
56
|
*/
|
|
57
57
|
@customElement('moni-fab')
|
|
58
58
|
export class MoniFab extends MoniElement {
|
|
59
59
|
private static _deprecationWarned = false;
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Define las dimensiones del FAB.
|
|
63
|
+
* `small` está deprecado en M3 Expressive.
|
|
64
|
+
* @type {'small' | 'medium' | 'large'}
|
|
65
|
+
* @default 'medium'
|
|
66
|
+
*/
|
|
61
67
|
@property({ reflect: true })
|
|
62
68
|
size: 'small' | 'medium' | 'large' = 'medium';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Mapeo de color semántico del FAB.
|
|
72
|
+
* `surface` está deprecado en M3.
|
|
73
|
+
* @type {'primary' | 'secondary' | 'tertiary' | 'surface'}
|
|
74
|
+
* @default 'primary'
|
|
75
|
+
*/
|
|
63
76
|
@property({ reflect: true })
|
|
64
77
|
color: 'primary' | 'secondary' | 'tertiary' | 'surface' = 'primary';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Forma de las esquinas del FAB.
|
|
81
|
+
* `circle` está deprecado en M3 (los FABs deben ser cuadrados redondeados).
|
|
82
|
+
* @type {'rounded' | 'circle'}
|
|
83
|
+
* @default 'rounded'
|
|
84
|
+
*/
|
|
65
85
|
@property({ reflect: true })
|
|
66
86
|
shape: 'rounded' | 'circle' = 'rounded';
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Alias heredado de `expanded`. Indica si el FAB muestra una etiqueta de texto.
|
|
90
|
+
* @type {boolean}
|
|
91
|
+
*/
|
|
67
92
|
@property({ type: Boolean, reflect: true }) extended = false;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Si es true, expande el FAB para mostrar su etiqueta de texto junto al icono.
|
|
96
|
+
* @type {boolean}
|
|
97
|
+
*/
|
|
68
98
|
@property({ type: Boolean, reflect: true }) expanded = false;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Deshabilita el FAB.
|
|
102
|
+
* @type {boolean}
|
|
103
|
+
*/
|
|
69
104
|
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Nombre del icono (Material Symbols) mostrado dentro del FAB.
|
|
108
|
+
* @type {string}
|
|
109
|
+
* @default 'add'
|
|
110
|
+
*/
|
|
70
111
|
@property({ reflect: true }) icon = 'add';
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Etiqueta de texto mostrada cuando el FAB se expande/extiende.
|
|
115
|
+
* @type {string}
|
|
116
|
+
*/
|
|
71
117
|
@property({ reflect: true }) label = '';
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Fija la posición del FAB relativa a los bordes de la pantalla.
|
|
121
|
+
* @type {'bottom-trailing' | 'bottom-leading' | 'top-trailing' | 'top-leading'}
|
|
122
|
+
* @default 'bottom-trailing'
|
|
123
|
+
*/
|
|
72
124
|
@property({ reflect: true })
|
|
73
125
|
position:
|
|
74
126
|
| 'bottom-trailing'
|
|
@@ -128,7 +180,7 @@ export class MoniFab extends MoniElement {
|
|
|
128
180
|
font-size: 1rem;
|
|
129
181
|
font-weight: 500;
|
|
130
182
|
color: var(--on-primary);
|
|
131
|
-
/*
|
|
183
|
+
/* Por defecto: sin relleno para que el icono se centre; el relleno se añade cuando existe etiqueta */
|
|
132
184
|
padding: 0 1.5rem;
|
|
133
185
|
background-color: var(--primary);
|
|
134
186
|
border: 0.0625rem solid transparent;
|
|
@@ -158,7 +210,7 @@ export class MoniFab extends MoniElement {
|
|
|
158
210
|
box-shadow: var(--elevate1);
|
|
159
211
|
}
|
|
160
212
|
|
|
161
|
-
/*
|
|
213
|
+
/* Cuando no hay etiqueta: solo icono, eliminar relleno horizontal para que el icono se centre */
|
|
162
214
|
.button:not(:has(> .label:not(:empty))) {
|
|
163
215
|
padding-inline: 0;
|
|
164
216
|
}
|
|
@@ -227,8 +279,8 @@ export class MoniFab extends MoniElement {
|
|
|
227
279
|
color: inherit;
|
|
228
280
|
}
|
|
229
281
|
|
|
230
|
-
/*
|
|
231
|
-
|
|
282
|
+
/* La etiqueta siempre se renderiza pero la cadena vacía se oculta mediante overflow.
|
|
283
|
+
Cuando se establece la propiedad label, aparece. */
|
|
232
284
|
.label {
|
|
233
285
|
white-space: nowrap;
|
|
234
286
|
max-inline-size: 16rem;
|
|
@@ -241,18 +293,32 @@ export class MoniFab extends MoniElement {
|
|
|
241
293
|
display: none;
|
|
242
294
|
}
|
|
243
295
|
|
|
244
|
-
/*
|
|
296
|
+
/* Forma circular: ocultar etiqueta completamente */
|
|
245
297
|
:host([shape='circle']) .label,
|
|
246
298
|
:host([shape='circle']) slot {
|
|
247
299
|
display: none !important;
|
|
248
300
|
}
|
|
249
|
-
/*
|
|
301
|
+
/* Círculo: eliminar relleno, hacerlo un círculo perfecto */
|
|
250
302
|
:host([shape='circle']) .button {
|
|
251
303
|
padding: 0 !important;
|
|
252
304
|
}
|
|
253
305
|
`
|
|
254
306
|
];
|
|
255
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Renderiza el botón FAB con su icono, etiqueta extendida opcional, y estado de carga.
|
|
310
|
+
*
|
|
311
|
+
* **Composición de clases:**
|
|
312
|
+
* - `'fab'` — estilos base del FAB (redondo, 56dp, elevado).
|
|
313
|
+
* - `this.size` (`'small'` = 40dp, `'large'` = 96dp, por defecto = 56dp).
|
|
314
|
+
* - `this.variant` — esquema de color (`'primary'`, `'secondary'`, `'tertiary'`, `'surface'`).
|
|
315
|
+
* - `'extended'` — se añade cuando se establece `label`; ensancha el FAB en forma de píldora
|
|
316
|
+
* y revela el texto de la etiqueta con una transición CSS `max-width`.
|
|
317
|
+
*
|
|
318
|
+
* **Estado de carga:**
|
|
319
|
+
* Cuando `loading=true`, un spinner `<moni-progress>` reemplaza el icono. La etiqueta
|
|
320
|
+
* permanece visible para mantener el ancho del FAB extendido y prevenir saltos de diseño.
|
|
321
|
+
*/
|
|
256
322
|
override render() {
|
|
257
323
|
const classes = [
|
|
258
324
|
'button',
|
|
@@ -14,7 +14,7 @@ describe('moni-file-field', () => {
|
|
|
14
14
|
el.remove();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it('
|
|
17
|
+
it('renderiza un input de texto y un input de archivo', async () => {
|
|
18
18
|
await el.updateComplete;
|
|
19
19
|
const text = el.shadowRoot?.querySelector('input[type="text"]');
|
|
20
20
|
const file = el.shadowRoot?.querySelector('input[type="file"]');
|
|
@@ -22,7 +22,7 @@ describe('moni-file-field', () => {
|
|
|
22
22
|
expect(file).toBeTruthy();
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it('
|
|
25
|
+
it('mantiene el input de texto en solo lectura (readonly)', async () => {
|
|
26
26
|
await el.updateComplete;
|
|
27
27
|
const text = el.shadowRoot?.querySelector(
|
|
28
28
|
'input[type="text"]'
|
|
@@ -30,7 +30,7 @@ describe('moni-file-field', () => {
|
|
|
30
30
|
expect(text.hasAttribute('readonly')).toBe(true);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it('
|
|
33
|
+
it('coloca el label entre el input de texto y el input de archivo para la elevación del label', async () => {
|
|
34
34
|
el.label = 'Upload';
|
|
35
35
|
await el.updateComplete;
|
|
36
36
|
const text = el.shadowRoot?.querySelector('input[type="text"]');
|
|
@@ -40,14 +40,14 @@ describe('moni-file-field', () => {
|
|
|
40
40
|
expect(label?.nextElementSibling).toBe(file);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
it('
|
|
43
|
+
it('agrega la clase active al input de texto cuando se establece el valor', async () => {
|
|
44
44
|
el.value = 'report.pdf';
|
|
45
45
|
await el.updateComplete;
|
|
46
46
|
const text = el.shadowRoot?.querySelector('input[type="text"]');
|
|
47
47
|
expect(text?.classList.contains('active')).toBe(true);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it('
|
|
50
|
+
it('reenvía el atributo accept al input de archivo', async () => {
|
|
51
51
|
el.accept = 'image/*';
|
|
52
52
|
await el.updateComplete;
|
|
53
53
|
const file = el.shadowRoot?.querySelector(
|
|
@@ -56,7 +56,7 @@ describe('moni-file-field', () => {
|
|
|
56
56
|
expect(file.accept).toBe('image/*');
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
it('
|
|
59
|
+
it('reenvía el atributo multiple al input de archivo', async () => {
|
|
60
60
|
el.multiple = true;
|
|
61
61
|
await el.updateComplete;
|
|
62
62
|
const file = el.shadowRoot?.querySelector(
|
|
@@ -65,13 +65,13 @@ describe('moni-file-field', () => {
|
|
|
65
65
|
expect(file.multiple).toBe(true);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
it('
|
|
68
|
+
it('muestra un icono de carpeta (trailing folder icon) por defecto', async () => {
|
|
69
69
|
await el.updateComplete;
|
|
70
70
|
const trailing = el.shadowRoot?.querySelector('.trailing-icon');
|
|
71
71
|
expect(trailing).toBeTruthy();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it('
|
|
74
|
+
it('usa button-label como placeholder cuando no hay valor', async () => {
|
|
75
75
|
await el.updateComplete;
|
|
76
76
|
const text = el.shadowRoot?.querySelector(
|
|
77
77
|
'input[type="text"]'
|