@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
|
@@ -10,125 +10,244 @@ import { customElement, property, state, query } from 'lit/decorators.js';
|
|
|
10
10
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
11
11
|
import { classMap } from 'lit/directives/class-map.js';
|
|
12
12
|
import { MoniElement, sharedStyles, fieldStyles } from './_base/index.js';
|
|
13
|
+
import { emitMoniEvent } from '../utils/event-emitter.js';
|
|
13
14
|
import './moni-icon.js';
|
|
14
15
|
import './moni-progress.js';
|
|
15
16
|
import './moni-select-option.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
+
* Representación interna de una opción individual dentro del menú desplegable del select.
|
|
19
20
|
*
|
|
20
21
|
* @internal
|
|
21
22
|
*/
|
|
22
23
|
interface OptionNode {
|
|
23
|
-
/**
|
|
24
|
+
/** Propiedad discriminante que identifica este nodo como una opción. */
|
|
24
25
|
type: 'option';
|
|
25
|
-
/**
|
|
26
|
+
/** El valor enviado cuando se selecciona esta opción. */
|
|
26
27
|
value: string;
|
|
27
|
-
/**
|
|
28
|
+
/** La etiqueta mostrada en la lista desplegable. */
|
|
28
29
|
label: string;
|
|
29
|
-
/**
|
|
30
|
+
/** Cuando es `true`, la opción no es interactiva y aparece atenuada (gris). */
|
|
30
31
|
disabled?: boolean;
|
|
31
|
-
/**
|
|
32
|
+
/** Referencia al elemento original `<moni-select-option>` de la ranura (slot), si lo hay. */
|
|
32
33
|
element?: HTMLElement;
|
|
33
|
-
/**
|
|
34
|
+
/** Nombre de grupo opcional para organizar opciones bajo un `<moni-select-group>`. */
|
|
34
35
|
group?: string;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
+
* Representación interna de un grupo de opciones dentro del menú desplegable del select.
|
|
39
40
|
*
|
|
40
41
|
* @internal
|
|
41
42
|
*/
|
|
42
43
|
interface GroupNode {
|
|
43
|
-
/**
|
|
44
|
+
/** Propiedad discriminante que identifica este nodo como un grupo. */
|
|
44
45
|
type: 'group';
|
|
45
|
-
/**
|
|
46
|
+
/** La etiqueta de encabezado del grupo mostrada sobre las opciones del grupo. */
|
|
46
47
|
label: string;
|
|
47
|
-
/**
|
|
48
|
+
/** Las opciones anidadas dentro de este grupo. */
|
|
48
49
|
children: DropdownNode[];
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
/**
|
|
52
|
+
/** Tipo de unión para cualquier nodo en el árbol del menú desplegable. @internal */
|
|
52
53
|
type DropdownNode = OptionNode | GroupNode;
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
* Material Design 3 Select (
|
|
56
|
+
* Componente Material Design 3 Select (Menú Desplegable).
|
|
56
57
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* M3
|
|
58
|
+
* Un menú desplegable select personalizado completo con capacidad de búsqueda, grupos de opciones,
|
|
59
|
+
* navegación por teclado, animación y un modo de cajón/hoja (drawer/sheet) móvil opcional.
|
|
60
|
+
* Reemplaza el elemento nativo `<select>` con una alternativa completamente estilizada y accesible
|
|
61
|
+
* que cumple con M3.
|
|
61
62
|
*
|
|
62
|
-
* **
|
|
63
|
+
* **Referencia a la especificación M3:** `m3-docs/components/menus/specs.md` (menús desplegables)
|
|
63
64
|
*
|
|
64
|
-
* **
|
|
65
|
-
* - Filled
|
|
66
|
-
* -
|
|
67
|
-
* -
|
|
68
|
-
* -
|
|
69
|
-
* -
|
|
70
|
-
*
|
|
71
|
-
* -
|
|
72
|
-
* -
|
|
73
|
-
* -
|
|
65
|
+
* **Resumen de características:**
|
|
66
|
+
* - Variantes Filled y Outlined que coinciden con los estilos de campo de texto de M3.
|
|
67
|
+
* - Etiqueta flotante (floating label) con la animación estándar de etiqueta flotante de estilos de campo.
|
|
68
|
+
* - Modo de búsqueda: el atributo `searchable` agrega un campo de entrada de filtro en línea.
|
|
69
|
+
* - Grupos de opciones: elementos `<moni-select-group>` en el slot para opciones jerárquicas.
|
|
70
|
+
* - Cajón móvil: el atributo `drawer` abre las opciones en un `<moni-bottom-sheet>`
|
|
71
|
+
* en lugar de un popup desplegable, ideal para interfaces táctiles (touch UIs).
|
|
72
|
+
* - Navegación por teclado: Teclas de flecha, Enter, Escape y Tab según combobox de ARIA.
|
|
73
|
+
* - Estado de carga: `loading` muestra un progreso circular indeterminado.
|
|
74
|
+
* - Soporte multi-valor: `multiple` permite selección múltiple.
|
|
74
75
|
*
|
|
75
|
-
* **
|
|
76
|
-
*
|
|
77
|
-
* 1. **
|
|
78
|
-
* 2.
|
|
76
|
+
* **Fuentes de opciones:**
|
|
77
|
+
* Las opciones se pueden proporcionar de dos maneras:
|
|
78
|
+
* 1. **Elementos `<moni-select-option>` en la ranura (slot)** (por defecto, recomendado para SSR).
|
|
79
|
+
* 2. **Propiedad `options`** — un array de `DropdownNode[]` para un control completamente programático.
|
|
79
80
|
*
|
|
80
|
-
* **Value binding:**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* `'change'
|
|
81
|
+
* **Vinculación de valores (Value binding):**
|
|
82
|
+
* La propiedad `value` contiene la cadena de valor de la opción actualmente seleccionada.
|
|
83
|
+
* Para selección múltiple, `values` contiene `string[]`. Al cambiar, se dispara un
|
|
84
|
+
* evento compuesto `'moni-change'`.
|
|
84
85
|
*
|
|
85
|
-
* @fires change -
|
|
86
|
-
*
|
|
86
|
+
* @fires moni-change - Burbujea y está compuesto. Se dispara cuando el valor seleccionado cambia.
|
|
87
|
+
* Lee `element.value` (o `element.values` para `multiple`).
|
|
87
88
|
*
|
|
88
89
|
* @example
|
|
89
90
|
* ```html
|
|
90
|
-
* <moni-select label="
|
|
91
|
-
* <moni-select-option value="us">
|
|
92
|
-
* <moni-select-option value="gb">
|
|
93
|
-
* <moni-select-option value="de">
|
|
91
|
+
* <moni-select label="País" name="country" variant="outlined">
|
|
92
|
+
* <moni-select-option value="us">Estados Unidos</moni-select-option>
|
|
93
|
+
* <moni-select-option value="gb">Reino Unido</moni-select-option>
|
|
94
|
+
* <moni-select-option value="de">Alemania</moni-select-option>
|
|
94
95
|
* </moni-select>
|
|
95
96
|
*
|
|
96
|
-
* <!--
|
|
97
|
-
* <moni-select label="
|
|
97
|
+
* <!-- Select con búsqueda -->
|
|
98
|
+
* <moni-select label="Idioma" searchable>
|
|
98
99
|
* <moni-select-option value="ts">TypeScript</moni-select-option>
|
|
99
100
|
* <moni-select-option value="py">Python</moni-select-option>
|
|
100
101
|
* </moni-select>
|
|
101
102
|
* ```
|
|
102
103
|
*
|
|
103
|
-
* @slot default - `<moni-select-option>`
|
|
104
|
+
* @slot default - Hijos `<moni-select-option>` o `<moni-select-group>`.
|
|
104
105
|
*
|
|
105
|
-
* @csspart field -
|
|
106
|
-
* @csspart dropdown -
|
|
106
|
+
* @csspart field - El contenedor div exterior `.field`.
|
|
107
|
+
* @csspart dropdown - El contenedor flotante de la lista de opciones.
|
|
107
108
|
*/
|
|
108
109
|
@customElement('moni-select')
|
|
109
110
|
export class MoniSelect extends MoniElement {
|
|
111
|
+
static formAssociated = true;
|
|
112
|
+
private _internals: ElementInternals;
|
|
113
|
+
|
|
114
|
+
constructor() {
|
|
115
|
+
super();
|
|
116
|
+
this._internals = this.attachInternals();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Nombre del campo select, usado para el envío del formulario.
|
|
121
|
+
* @type {string}
|
|
122
|
+
*/
|
|
110
123
|
@property({ reflect: true }) name = '';
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Texto de la etiqueta flotante.
|
|
127
|
+
* @type {string}
|
|
128
|
+
*/
|
|
111
129
|
@property({ reflect: true }) label = '';
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Variante visual del campo select.
|
|
133
|
+
* @type {'filled' | 'outlined'}
|
|
134
|
+
* @default 'filled'
|
|
135
|
+
*/
|
|
112
136
|
@property({ reflect: true }) variant: 'filled' | 'outlined' = 'filled';
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Define las dimensiones del campo select.
|
|
140
|
+
* @type {'small' | 'medium' | 'large' | 'extra'}
|
|
141
|
+
* @default 'medium'
|
|
142
|
+
*/
|
|
113
143
|
@property({ reflect: true })
|
|
114
144
|
size: 'small' | 'medium' | 'large' | 'extra' = 'medium';
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Forma (radio de borde) del campo.
|
|
148
|
+
* @type {'round' | 'square' | 'no-round'}
|
|
149
|
+
* @default 'no-round'
|
|
150
|
+
*/
|
|
115
151
|
@property({ reflect: true })
|
|
116
152
|
shape: 'round' | 'square' | 'no-round' = 'no-round';
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Deshabilita el campo select.
|
|
156
|
+
* @type {boolean}
|
|
157
|
+
*/
|
|
117
158
|
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Si es verdadero, muestra un indicador de carga (progreso lineal).
|
|
162
|
+
* @type {boolean}
|
|
163
|
+
*/
|
|
118
164
|
@property({ type: Boolean, reflect: true }) loading = false;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Texto de ayuda mostrado debajo del campo.
|
|
168
|
+
* @type {string}
|
|
169
|
+
*/
|
|
119
170
|
@property({ reflect: true }) helper = '';
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Texto de error mostrado debajo del campo cuando `error` es true.
|
|
174
|
+
* Reemplaza al texto de ayuda.
|
|
175
|
+
* @type {string}
|
|
176
|
+
*/
|
|
120
177
|
@property({ reflect: true, attribute: 'error-text' }) errorText = '';
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Si es verdadero, establece el campo en un estado de error.
|
|
181
|
+
* @type {boolean}
|
|
182
|
+
*/
|
|
121
183
|
@property({ type: Boolean, reflect: true }) error = false;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* El valor actual del select.
|
|
187
|
+
* @type {string}
|
|
188
|
+
*/
|
|
122
189
|
@property({ reflect: true }) value = '';
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Nombre del icono principal (leading icon, Material Symbols).
|
|
193
|
+
* @type {string}
|
|
194
|
+
*/
|
|
123
195
|
@property({ reflect: true }) icon = '';
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Nombre del icono final (trailing icon, Material Symbols) que indica el estado del menú desplegable.
|
|
199
|
+
* @type {string}
|
|
200
|
+
* @default 'arrow_drop_down'
|
|
201
|
+
*/
|
|
124
202
|
@property({ reflect: true, attribute: 'trailing-icon' }) trailingIcon =
|
|
125
203
|
'arrow_drop_down';
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Habilita un campo de búsqueda en la parte superior del menú desplegable para filtrar opciones.
|
|
207
|
+
* @type {boolean}
|
|
208
|
+
*/
|
|
126
209
|
@property({ type: Boolean, reflect: true }) searchable = false;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Muestra un botón de limpieza cuando se selecciona un valor para restablecer fácilmente el campo.
|
|
213
|
+
* @type {boolean}
|
|
214
|
+
*/
|
|
127
215
|
@property({ type: Boolean, reflect: true }) clearable = false;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Renderiza las opciones como una hoja inferior (bottom sheet, ideal para dispositivos móviles) en lugar de un menú desplegable.
|
|
219
|
+
* @type {boolean}
|
|
220
|
+
*/
|
|
128
221
|
@property({ type: Boolean, reflect: true }) sheet = false;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Texto de marcador de posición (placeholder) mostrado cuando no hay ningún valor seleccionado.
|
|
225
|
+
* @type {string}
|
|
226
|
+
*/
|
|
129
227
|
@property({ reflect: true }) placeholder = '';
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Estrategia de posicionamiento para el menú desplegable.
|
|
231
|
+
* @type {'absolute' | 'fixed'}
|
|
232
|
+
* @default 'absolute'
|
|
233
|
+
*/
|
|
130
234
|
@property({ reflect: true }) positioning: 'absolute' | 'fixed' = 'absolute';
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Ubicación preferida del menú desplegable con respecto al disparador (trigger).
|
|
238
|
+
* @type {'top' | 'bottom' | 'left' | 'right' | 'auto'}
|
|
239
|
+
* @default 'auto'
|
|
240
|
+
*/
|
|
131
241
|
@property({ reflect: true }) placement: 'top' | 'bottom' | 'left' | 'right' | 'auto' = 'auto';
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Restricción de ancho del menú desplegable.
|
|
245
|
+
* - `'trigger'`: Coincide con el ancho del campo de entrada.
|
|
246
|
+
* - `'auto'`: Coincide con el ancho del contenido del menú desplegable.
|
|
247
|
+
* - O cualquier valor CSS de ancho válido (ej. '200px').
|
|
248
|
+
* @type {string}
|
|
249
|
+
* @default 'trigger'
|
|
250
|
+
*/
|
|
132
251
|
@property({ reflect: true, attribute: 'dropdown-width' }) dropdownWidth = 'trigger';
|
|
133
252
|
|
|
134
253
|
@state() private _open = false;
|
|
@@ -468,6 +587,12 @@ export class MoniSelect extends MoniElement {
|
|
|
468
587
|
`
|
|
469
588
|
];
|
|
470
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Hook del ciclo de vida (Lit).
|
|
592
|
+
* Vincula event listeners globales necesarios para la lógica del dropdown.
|
|
593
|
+
* Se usa `capture: true` en el evento de scroll para poder reaccionar
|
|
594
|
+
* al desplazamiento de cualquier contenedor padre y reposicionar el menú a tiempo.
|
|
595
|
+
*/
|
|
471
596
|
override connectedCallback() {
|
|
472
597
|
super.connectedCallback();
|
|
473
598
|
document.addEventListener('click', this._handleOutsideClick);
|
|
@@ -475,6 +600,11 @@ export class MoniSelect extends MoniElement {
|
|
|
475
600
|
window.addEventListener('resize', this._handleResize);
|
|
476
601
|
}
|
|
477
602
|
|
|
603
|
+
/**
|
|
604
|
+
* Hook del ciclo de vida (Lit).
|
|
605
|
+
* Desmonta rigurosamente todos los listeners globales para prevenir memory leaks
|
|
606
|
+
* o llamadas accidentales cuando el select ya fue destruido del DOM.
|
|
607
|
+
*/
|
|
478
608
|
override disconnectedCallback() {
|
|
479
609
|
super.disconnectedCallback();
|
|
480
610
|
document.removeEventListener('click', this._handleOutsideClick);
|
|
@@ -482,8 +612,17 @@ export class MoniSelect extends MoniElement {
|
|
|
482
612
|
window.removeEventListener('resize', this._handleResize);
|
|
483
613
|
}
|
|
484
614
|
|
|
615
|
+
/**
|
|
616
|
+
* Hook de actualización reactiva (Lit).
|
|
617
|
+
* Verifica si alguna propiedad relacionada con el tamaño del contenido (como la búsqueda,
|
|
618
|
+
* la ruta de anidación o el estado abierto) ha cambiado, para forzar un recálculo
|
|
619
|
+
* de la altura animada del contenedor `drilldown-wrapper`.
|
|
620
|
+
*/
|
|
485
621
|
override updated(changedProperties: Map<string | number | symbol, unknown>) {
|
|
486
622
|
super.updated(changedProperties);
|
|
623
|
+
if (changedProperties.has('value')) {
|
|
624
|
+
this._internals?.setFormValue?.(this.value);
|
|
625
|
+
}
|
|
487
626
|
if (
|
|
488
627
|
changedProperties.has('_open') ||
|
|
489
628
|
changedProperties.has('_drilldownPath') ||
|
|
@@ -495,6 +634,12 @@ export class MoniSelect extends MoniElement {
|
|
|
495
634
|
}
|
|
496
635
|
}
|
|
497
636
|
|
|
637
|
+
/**
|
|
638
|
+
* Lógica de animación de altura (Height Animation).
|
|
639
|
+
* Cuando se navega entre categorías anidadas (`_drilldownPath`), las listas hijas pueden
|
|
640
|
+
* tener diferentes tamaños. Este método lee el `scrollHeight` del panel activo y lo aplica
|
|
641
|
+
* imperativamente al `wrapper` para disparar una transición CSS suave en la altura del dropdown.
|
|
642
|
+
*/
|
|
498
643
|
private _updateWrapperHeight() {
|
|
499
644
|
const wrapper = this.shadowRoot?.querySelector('.drilldown-wrapper') as HTMLElement;
|
|
500
645
|
if (!wrapper) return;
|
|
@@ -515,12 +660,28 @@ export class MoniSelect extends MoniElement {
|
|
|
515
660
|
});
|
|
516
661
|
}
|
|
517
662
|
|
|
663
|
+
/**
|
|
664
|
+
* Cierra el Dropdown cuando el usuario hace clic fuera de las dimensiones del componente.
|
|
665
|
+
* Utiliza `e.composedPath()` para poder atravesar barreras de Shadow DOM si el select
|
|
666
|
+
* está dentro de un contenedor web-component externo.
|
|
667
|
+
*/
|
|
518
668
|
private _handleOutsideClick = (e: MouseEvent) => {
|
|
519
669
|
if (this._open && !e.composedPath().includes(this)) {
|
|
520
670
|
this._closeDropdown();
|
|
521
671
|
}
|
|
522
672
|
};
|
|
523
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Motor de Cálculo de Colisiones de la Interfaz (Collision Detection).
|
|
676
|
+
*
|
|
677
|
+
* @logic
|
|
678
|
+
* 1. Calcula las coordenadas espaciales (`getBoundingClientRect`) del select base.
|
|
679
|
+
* 2. Compara el espacio disponible (`spaceAbove`, `spaceBelow`, `spaceRight`, `spaceLeft`)
|
|
680
|
+
* respecto al viewport (`window.innerHeight`, `window.innerWidth`).
|
|
681
|
+
* 3. Si el dropdown no cabe en la posición preferida (`placement`), revierte lógicamente
|
|
682
|
+
* la posición (ej: si `bottom` desborda, lo abre hacia `top`).
|
|
683
|
+
* 4. Actualiza `_actualPlacement` con la dirección segura calculada.
|
|
684
|
+
*/
|
|
524
685
|
private _determineActualPlacement() {
|
|
525
686
|
if (this.sheet) {
|
|
526
687
|
this._actualPlacement = 'bottom';
|
|
@@ -569,6 +730,11 @@ export class MoniSelect extends MoniElement {
|
|
|
569
730
|
}
|
|
570
731
|
}
|
|
571
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Handler de Scroll global.
|
|
735
|
+
* Fuerza un recálculo de la posición y dimensiones del dropdown si el usuario
|
|
736
|
+
* hace scroll en la página mientras el select permanece abierto.
|
|
737
|
+
*/
|
|
572
738
|
private _handleScroll = () => {
|
|
573
739
|
if (this._open) {
|
|
574
740
|
this._determineActualPlacement();
|
|
@@ -576,6 +742,11 @@ export class MoniSelect extends MoniElement {
|
|
|
576
742
|
}
|
|
577
743
|
};
|
|
578
744
|
|
|
745
|
+
/**
|
|
746
|
+
* Handler de Resize global.
|
|
747
|
+
* Asegura que el popover del select no quede fuera del viewport cuando
|
|
748
|
+
* se cambia el tamaño de la ventana o se rota la pantalla en dispositivos móviles.
|
|
749
|
+
*/
|
|
579
750
|
private _handleResize = () => {
|
|
580
751
|
if (this._open) {
|
|
581
752
|
this._determineActualPlacement();
|
|
@@ -583,6 +754,12 @@ export class MoniSelect extends MoniElement {
|
|
|
583
754
|
}
|
|
584
755
|
};
|
|
585
756
|
|
|
757
|
+
/**
|
|
758
|
+
* Aplica las coordenadas y dimensiones absolutas o fijas al menú desplegable.
|
|
759
|
+
* Este método es fundamental cuando `positioning === 'fixed'`, ya que el dropdown
|
|
760
|
+
* se saca del flujo normal del documento para evitar `overflow: hidden` de los padres.
|
|
761
|
+
* Mapea el `width` para coincidir opcionalmente con el gatillo (`dropdownWidth="trigger"`).
|
|
762
|
+
*/
|
|
586
763
|
private _updateMenuPosition() {
|
|
587
764
|
if (!this._open || this.sheet) return;
|
|
588
765
|
|
|
@@ -639,6 +816,12 @@ export class MoniSelect extends MoniElement {
|
|
|
639
816
|
}
|
|
640
817
|
}
|
|
641
818
|
|
|
819
|
+
/**
|
|
820
|
+
* Devuelve un string de estilos inline para el menú desplegable.
|
|
821
|
+
* Si la posición es `fixed` (calculada dinámicamente para evadir overflow),
|
|
822
|
+
* retorna el bloque de coordenadas generado por `_updateMenuPosition`.
|
|
823
|
+
* Si no, asigna anchos basados en la propiedad `dropdownWidth` (auto, trigger, custom).
|
|
824
|
+
*/
|
|
642
825
|
private _getMenuStyle() {
|
|
643
826
|
if (this.positioning === 'fixed') {
|
|
644
827
|
return this._menuStyle;
|
|
@@ -652,6 +835,18 @@ export class MoniSelect extends MoniElement {
|
|
|
652
835
|
return '';
|
|
653
836
|
}
|
|
654
837
|
|
|
838
|
+
/**
|
|
839
|
+
* Parseador del Shadow DOM (Slot Change Handler).
|
|
840
|
+
*
|
|
841
|
+
* @logic
|
|
842
|
+
* 1. Cuando los hijos proyectados cambian (ej. se agregan nuevos `<option>`),
|
|
843
|
+
* lee iterativamente todos los nodos del Light DOM.
|
|
844
|
+
* 2. Si encuentra `<optgroup>` o `<moni-select-option group="A/B">`, construye dinámicamente
|
|
845
|
+
* un árbol estructural de `GroupNode` anidados.
|
|
846
|
+
* 3. Si encuentra opciones, las inyecta en el grupo correspondiente del árbol.
|
|
847
|
+
* 4. Almacena el árbol resultante en `_parsedOptions` para que el componente
|
|
848
|
+
* pueda renderizar sus propios submenús nativos independientemente del markup inicial.
|
|
849
|
+
*/
|
|
655
850
|
private _onSlotChange() {
|
|
656
851
|
if (!this._slot) return;
|
|
657
852
|
const assigned = this._slot.assignedElements({ flatten: true });
|
|
@@ -705,6 +900,10 @@ export class MoniSelect extends MoniElement {
|
|
|
705
900
|
this.requestUpdate();
|
|
706
901
|
}
|
|
707
902
|
|
|
903
|
+
/**
|
|
904
|
+
* Alterna el estado de apertura/cierre del dropdown, previniendo
|
|
905
|
+
* la acción si el componente está deshabilitado.
|
|
906
|
+
*/
|
|
708
907
|
private _toggleDropdown(e?: Event) {
|
|
709
908
|
if (e) {
|
|
710
909
|
e.stopPropagation();
|
|
@@ -717,6 +916,11 @@ export class MoniSelect extends MoniElement {
|
|
|
717
916
|
}
|
|
718
917
|
}
|
|
719
918
|
|
|
919
|
+
/**
|
|
920
|
+
* Manejador de clics en el campo de texto (Trigger).
|
|
921
|
+
* Si el componente es `searchable` (permite escribir para buscar), un clic
|
|
922
|
+
* sólo abrirá el menú (no lo cerrará si ya estaba abierto, para permitir al usuario seguir escribiendo).
|
|
923
|
+
*/
|
|
720
924
|
private _onInputClick(e: MouseEvent) {
|
|
721
925
|
if (this.disabled) return;
|
|
722
926
|
if (this.searchable && !this.sheet) {
|
|
@@ -728,30 +932,43 @@ export class MoniSelect extends MoniElement {
|
|
|
728
932
|
}
|
|
729
933
|
}
|
|
730
934
|
|
|
935
|
+
/**
|
|
936
|
+
* Despliega el menú del select y coordina la accesibilidad, posicionamiento y foco inicial.
|
|
937
|
+
*/
|
|
731
938
|
private _openDropdown() {
|
|
732
939
|
this._open = true;
|
|
733
940
|
this._activeIndex = -1;
|
|
734
941
|
this._drilldownPath = [];
|
|
735
942
|
|
|
943
|
+
// Calculamos la dirección del menú emergente en base al espacio disponible en la pantalla
|
|
736
944
|
this._determineActualPlacement();
|
|
737
945
|
|
|
738
|
-
//
|
|
946
|
+
// Medimos dinámicamente si hay espacio suficiente para renderizar submenús laterales (Desktop).
|
|
947
|
+
// Si es móvil (<600px) o no hay 160px de espacio libre a los lados, forzamos un diseño plano ("inline categories")
|
|
948
|
+
// para evitar que los submenús se salgan del viewport.
|
|
739
949
|
const rect = this.getBoundingClientRect();
|
|
740
950
|
const spaceOnRight = window.innerWidth - rect.right;
|
|
741
951
|
const spaceOnLeft = rect.left;
|
|
742
952
|
this._useInlineCategories = window.innerWidth < 600 || (spaceOnRight < 160 && spaceOnLeft < 160);
|
|
743
953
|
|
|
954
|
+
// Si el menú tiene position="fixed", necesitamos computar y aplicar las coordenadas absolutas en pixels
|
|
744
955
|
if (this.positioning === 'fixed') {
|
|
745
956
|
this._updateMenuPosition();
|
|
746
957
|
}
|
|
958
|
+
|
|
959
|
+
// Lógica especial si el componente está configurado como "searchable" (Input filtrable)
|
|
747
960
|
if (this.searchable) {
|
|
748
961
|
const selectedOpt = this._findOptionByValue(this.value);
|
|
749
962
|
this._searchQuery = selectedOpt ? selectedOpt.label : '';
|
|
963
|
+
|
|
964
|
+
// Esperamos un tick para que el input o bottom-sheet sea renderizado antes de solicitar el foco
|
|
750
965
|
setTimeout(() => {
|
|
751
966
|
if (this._input && !this.sheet) {
|
|
967
|
+
// Foco en el input principal (Desktop) y seleccionamos todo el texto para fácil reemplazo
|
|
752
968
|
this._input.focus();
|
|
753
969
|
this._input.select();
|
|
754
970
|
} else if (this.sheet) {
|
|
971
|
+
// Foco en la caja de búsqueda inyectada dentro del bottom-sheet (Mobile)
|
|
755
972
|
const sheetInput = this.shadowRoot?.querySelector('.sheet-search-input') as HTMLInputElement;
|
|
756
973
|
if (sheetInput) {
|
|
757
974
|
sheetInput.focus();
|
|
@@ -762,6 +979,10 @@ export class MoniSelect extends MoniElement {
|
|
|
762
979
|
}
|
|
763
980
|
}
|
|
764
981
|
|
|
982
|
+
/**
|
|
983
|
+
* Cierra el dropdown y purga la consulta de búsqueda (`_searchQuery`)
|
|
984
|
+
* para que la próxima vez que se abra, la lista muestre todas las opciones por defecto.
|
|
985
|
+
*/
|
|
765
986
|
private _closeDropdown() {
|
|
766
987
|
this._open = false;
|
|
767
988
|
this._searchQuery = '';
|
|
@@ -772,6 +993,15 @@ export class MoniSelect extends MoniElement {
|
|
|
772
993
|
return this._getFlatOptions(this._parsedOptions).find(opt => opt.value === value);
|
|
773
994
|
}
|
|
774
995
|
|
|
996
|
+
/**
|
|
997
|
+
* Evento de input (tecleo) en la barra de búsqueda.
|
|
998
|
+
*
|
|
999
|
+
* @logic
|
|
1000
|
+
* 1. Actualiza `_searchQuery` y fuerza la apertura del dropdown.
|
|
1001
|
+
* 2. Si es `clearable` y el usuario borró todo, emite eventos vacíos.
|
|
1002
|
+
* 3. Busca coincidencias exactas en el texto. Si el usuario teclea exactamente el
|
|
1003
|
+
* label de una opción existente, la selecciona automáticamente (auto-completado nativo).
|
|
1004
|
+
*/
|
|
775
1005
|
private _onSearchInput(e: Event) {
|
|
776
1006
|
const query = (e.target as HTMLInputElement).value;
|
|
777
1007
|
this._searchQuery = query;
|
|
@@ -780,8 +1010,7 @@ export class MoniSelect extends MoniElement {
|
|
|
780
1010
|
|
|
781
1011
|
if (this.clearable && query.trim() === '') {
|
|
782
1012
|
this.value = '';
|
|
783
|
-
this
|
|
784
|
-
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
1013
|
+
emitMoniEvent(this, 'moni-change', { detail: { value: this.value, originalEvent: e } });
|
|
785
1014
|
return;
|
|
786
1015
|
}
|
|
787
1016
|
|
|
@@ -794,6 +1023,11 @@ export class MoniSelect extends MoniElement {
|
|
|
794
1023
|
}
|
|
795
1024
|
}
|
|
796
1025
|
|
|
1026
|
+
/**
|
|
1027
|
+
* Confirma la selección de una opción específica.
|
|
1028
|
+
* Actualiza el valor (estado reactivo), cierra el dropdown y despacha los eventos
|
|
1029
|
+
* estándar HTML `input` y `change` para integrarse fluidamente con formularios nativos o frameworks.
|
|
1030
|
+
*/
|
|
797
1031
|
private _selectOption(option: OptionNode) {
|
|
798
1032
|
if (option.disabled) return;
|
|
799
1033
|
this.value = option.value;
|
|
@@ -801,8 +1035,15 @@ export class MoniSelect extends MoniElement {
|
|
|
801
1035
|
|
|
802
1036
|
this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
|
803
1037
|
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
1038
|
+
emitMoniEvent(this, 'moni-change', { detail: { value: this.value } });
|
|
804
1039
|
}
|
|
805
1040
|
|
|
1041
|
+
/**
|
|
1042
|
+
* Maneja el Hover sobre categorías en modo Desktop (Submenús emergentes).
|
|
1043
|
+
* Detecta dinámicamente si el submenú se saldrá del lado derecho de la pantalla.
|
|
1044
|
+
* Si es así, altera las clases de CSS (`open-left` vs `open-right`) para que
|
|
1045
|
+
* el menú flote en la dirección opuesta, evitando desbordamientos de viewport (Overflow prevention).
|
|
1046
|
+
*/
|
|
806
1047
|
private _onSubmenuMouseEnter(e: MouseEvent) {
|
|
807
1048
|
const headerEl = e.currentTarget as HTMLElement;
|
|
808
1049
|
const submenu = headerEl.querySelector(':scope > .submenu') as HTMLElement;
|
|
@@ -822,41 +1063,56 @@ export class MoniSelect extends MoniElement {
|
|
|
822
1063
|
}
|
|
823
1064
|
}
|
|
824
1065
|
|
|
1066
|
+
/**
|
|
1067
|
+
* Gestiona la navegación completa por teclado siguiendo el patrón WAI-ARIA Combobox.
|
|
1068
|
+
* Soporta flechas direccionales, selección (Enter), y cierre (Escape).
|
|
1069
|
+
*/
|
|
825
1070
|
private _onKeyDown(e: KeyboardEvent) {
|
|
826
1071
|
if (this.disabled) return;
|
|
827
1072
|
|
|
1073
|
+
// Obtenemos solo las opciones que están visualmente disponibles en la jerarquía actual
|
|
828
1074
|
const visible = this._getVisibleOptions();
|
|
829
1075
|
|
|
830
1076
|
if (e.key === 'ArrowDown') {
|
|
831
1077
|
e.preventDefault();
|
|
832
1078
|
if (!this._open) {
|
|
1079
|
+
// Si está cerrado, ArrowDown lo abre (comportamiento estándar nativo)
|
|
833
1080
|
this._openDropdown();
|
|
834
1081
|
} else if (visible.length > 0) {
|
|
1082
|
+
// Navegamos circularmente hacia abajo
|
|
835
1083
|
this._activeIndex = (this._activeIndex + 1) % visible.length;
|
|
836
1084
|
this._scrollToActive();
|
|
837
1085
|
}
|
|
838
1086
|
} else if (e.key === 'ArrowUp') {
|
|
839
1087
|
e.preventDefault();
|
|
840
1088
|
if (this._open && visible.length > 0) {
|
|
1089
|
+
// Navegamos circularmente hacia arriba (+visible.length evita valores negativos en JS)
|
|
841
1090
|
this._activeIndex = (this._activeIndex - 1 + visible.length) % visible.length;
|
|
842
1091
|
this._scrollToActive();
|
|
843
1092
|
}
|
|
844
1093
|
} else if (e.key === 'Enter') {
|
|
845
1094
|
e.preventDefault();
|
|
846
1095
|
if (this._open) {
|
|
1096
|
+
// Si está abierto y hay una opción resaltada, simulamos el click para seleccionarla
|
|
847
1097
|
if (this._activeIndex >= 0 && this._activeIndex < visible.length) {
|
|
848
1098
|
this._selectOption(visible[this._activeIndex]);
|
|
849
1099
|
}
|
|
850
1100
|
} else {
|
|
1101
|
+
// Enter cuando está cerrado simplemente abre el menú
|
|
851
1102
|
this._openDropdown();
|
|
852
1103
|
}
|
|
853
1104
|
} else if (e.key === 'Escape') {
|
|
854
1105
|
e.preventDefault();
|
|
1106
|
+
// Escapa el menú sin hacer ninguna selección
|
|
855
1107
|
this._closeDropdown();
|
|
856
1108
|
}
|
|
857
1109
|
}
|
|
858
1110
|
|
|
1111
|
+
/**
|
|
1112
|
+
* Asegura que el elemento resaltado por teclado sea visible dentro del contenedor scrolleable.
|
|
1113
|
+
*/
|
|
859
1114
|
private _scrollToActive() {
|
|
1115
|
+
// Se requiere setTimeout para darle tiempo a Lit a renderizar la clase 'active-nav'
|
|
860
1116
|
setTimeout(() => {
|
|
861
1117
|
const activeEl = this.shadowRoot?.querySelector('.option-item.active-nav');
|
|
862
1118
|
if (activeEl) {
|
|
@@ -865,6 +1121,10 @@ export class MoniSelect extends MoniElement {
|
|
|
865
1121
|
}, 0);
|
|
866
1122
|
}
|
|
867
1123
|
|
|
1124
|
+
/**
|
|
1125
|
+
* Aplana el árbol jerárquico recursivo de <moni-select-group> a un array unidimensional de opciones puras.
|
|
1126
|
+
* Esencial para que la búsqueda por texto funcione globalmente ignorando las categorías.
|
|
1127
|
+
*/
|
|
868
1128
|
private _getFlatOptions(nodes: DropdownNode[]): OptionNode[] {
|
|
869
1129
|
const flat: OptionNode[] = [];
|
|
870
1130
|
const traverse = (n: DropdownNode) => {
|
|
@@ -902,6 +1162,13 @@ export class MoniSelect extends MoniElement {
|
|
|
902
1162
|
return filtered;
|
|
903
1163
|
}
|
|
904
1164
|
|
|
1165
|
+
/**
|
|
1166
|
+
* Motor de Renderizado: Lista Raíz (Modo Móvil / Drilldown).
|
|
1167
|
+
*
|
|
1168
|
+
* Renderiza las opciones iniciales y los grupos base de la categoría actual (`_drilldownPath`).
|
|
1169
|
+
* Asigna clases dinámicas (`selected`, `active-nav`) para la retroalimentación visual
|
|
1170
|
+
* del teclado y el valor actualmente seleccionado.
|
|
1171
|
+
*/
|
|
905
1172
|
private _renderRootList() {
|
|
906
1173
|
let visibleIndex = 0;
|
|
907
1174
|
return this._parsedOptions.map(node => {
|
|
@@ -928,6 +1195,13 @@ export class MoniSelect extends MoniElement {
|
|
|
928
1195
|
});
|
|
929
1196
|
}
|
|
930
1197
|
|
|
1198
|
+
/**
|
|
1199
|
+
* Motor de Renderizado: Subcategorías (Modo Móvil / Drilldown).
|
|
1200
|
+
*
|
|
1201
|
+
* A diferencia de desktop (donde los submenús flotan a los lados), en dispositivos
|
|
1202
|
+
* móviles o espacios constreñidos, el menú transiciona in-situ. Este método renderiza
|
|
1203
|
+
* el header del grupo activo, un botón de "Regresar" (Arrow Back) y las opciones hijas.
|
|
1204
|
+
*/
|
|
931
1205
|
private _renderSubcategoryList() {
|
|
932
1206
|
if (this._drilldownPath.length === 0) return nothing;
|
|
933
1207
|
const activeGroup = this._drilldownPath[this._drilldownPath.length - 1];
|
|
@@ -995,6 +1269,14 @@ export class MoniSelect extends MoniElement {
|
|
|
995
1269
|
`;
|
|
996
1270
|
}
|
|
997
1271
|
|
|
1272
|
+
/**
|
|
1273
|
+
* Orquestador principal de renderizado de la lista de opciones.
|
|
1274
|
+
*
|
|
1275
|
+
* Si el usuario introdujo texto (`_searchQuery`), renderiza los resultados filtrados.
|
|
1276
|
+
* Si la lista original está vacía, muestra un mensaje amigable (Empty State).
|
|
1277
|
+
* Determina condicionalmente si usar el modo plano/Drilldown (Móviles) o
|
|
1278
|
+
* el modo Desktop con submenús flotantes (`_renderDesktopNode`).
|
|
1279
|
+
*/
|
|
998
1280
|
private _renderOptionsList(filtered: OptionNode[]) {
|
|
999
1281
|
if (this._parsedOptions.length === 0) {
|
|
1000
1282
|
return html`<li class="no-options">No options found</li>`;
|
|
@@ -1049,6 +1331,35 @@ export class MoniSelect extends MoniElement {
|
|
|
1049
1331
|
return this._parsedOptions.map(node => this._renderDesktopNode(node, flatIndexRef));
|
|
1050
1332
|
}
|
|
1051
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* Ensambla el Shadow DOM del campo select, coordinando dos modos de interfaz de usuario distintos.
|
|
1336
|
+
*
|
|
1337
|
+
* **Resolución del valor a mostrar (Display value):**
|
|
1338
|
+
* El contenido de texto visible en el input está determinado por prioridad:
|
|
1339
|
+
* 1. Mientras el menú desplegable está abierto Y `searchable=true` Y NO es modo sheet:
|
|
1340
|
+
* muestra `_searchQuery` (el texto del filtro en vivo que el usuario está escribiendo).
|
|
1341
|
+
* 2. De lo contrario: muestra `selectedOpt.label` (la etiqueta de la opción actualmente
|
|
1342
|
+
* seleccionada) o vuelve a la cadena vacía.
|
|
1343
|
+
*
|
|
1344
|
+
* **Dos rutas de renderizado:**
|
|
1345
|
+
* - **Modo Sheet (`sheet=true`):** Renderiza un `<moni-bottom-sheet>` como un
|
|
1346
|
+
* cajón (drawer) superpuesto a pantalla completa, ideal para IU táctiles móviles. La lista de opciones
|
|
1347
|
+
* se renderiza dentro de la hoja en lugar del menú desplegable.
|
|
1348
|
+
* - **Modo desplegable (por defecto):** Renderiza un `.dropdown-menu` flotante que
|
|
1349
|
+
* se abre debajo o arriba del campo. La cadena `_menuStyle` (calculada por
|
|
1350
|
+
* `_computeMenuStyle()`) inyecta los `top`, `left`, `width`, y
|
|
1351
|
+
* sobreescrituras `position: fixed` correctas para contenedores con scroll (scroll-contained) y parents con overflow cortado (overflow-clipping).
|
|
1352
|
+
*
|
|
1353
|
+
* **Patrón combobox ARIA:**
|
|
1354
|
+
* El input de texto lleva `role="combobox"`, `aria-haspopup="listbox"` y
|
|
1355
|
+
* `aria-expanded` para satisfacer los requisitos del combobox WCAG 2.1 para lectores de pantalla.
|
|
1356
|
+
* `aria-activedescendant` se establece al ID de la opción actualmente enfocada por teclado
|
|
1357
|
+
* para habilitar el movimiento del cursor virtual sin gestión del foco.
|
|
1358
|
+
*
|
|
1359
|
+
* **Composición de `fieldClasses`:**
|
|
1360
|
+
* Sigue la convención de nomenclatura de clases de estilos de campo de BeerCSS. `prefix` se agrega
|
|
1361
|
+
* cuando `icon` está establecido (para desplazar el relleno -padding- inline-start del input de texto para el icono).
|
|
1362
|
+
*/
|
|
1052
1363
|
override render() {
|
|
1053
1364
|
const hasLeading = Boolean(this.icon);
|
|
1054
1365
|
const filtered = this._getFilteredOptions();
|