Combobox
Selector con búsqueda typeahead para colecciones dinámicas que crecen con los datos (colaboradores, puestos, departamentos, cuentas contables). Para enums y listas cortas (<~20 opciones fijas) usá un <select class="cl-input"> nativo — un typeahead de 3 opciones es peor UX que un select.
El DS distribuye solo el patrón visual: las clases .cl-combobox* y el contrato de markup/ARIA de abajo. El comportamiento (filtrado, teclado, estado ARIA) lo aporta la app consumidora con su propio JS, siguiendo el patrón combobox editable de WAI-ARIA APG.
Degradación sin JS
Renderizá server-side un <select class="cl-input"> nativo dentro del wrapper y dejá que el JS de la app lo reemplace al conectar: sin JS el select sigue funcionando. El select queda oculto pero en el DOM (es el control que viaja en el form); el input de texto y la lista los construye el JS.
<div class="cl-combobox">
<select class="cl-input" name="employee[position_id]">
<option value=""></option>
<option value="3" data-meta="Ventas">Gerente de Ventas</option>
</select>
</div>
Anatomía (post-enhancement)
Input con role="combobox" + lista popover con role="listbox". La lista se oculta con el atributo nativo hidden. El resaltado de teclado usa .is-active (foco virtual vía aria-activedescendant); la opción elegida lleva aria-selected="true". El demo muestra el estado abierto congelado.
- Gerente Administrativo
- Gerente de Ventas
<div class="cl-combobox">
<select class="cl-input" name="employee[position_id]" hidden></select>
<input class="cl-input" type="text" role="combobox"
aria-expanded="true" aria-controls="positions-list"
aria-autocomplete="list" aria-activedescendant="positions-list-opt-3"
autocomplete="off" placeholder="Buscar puesto...">
<ul class="cl-combobox-list" id="positions-list" role="listbox">
<li class="cl-combobox-option" role="option" id="positions-list-opt-1">
<span>Analista de Nómina</span>
<span class="cl-combobox-option-meta">Administración</span>
</li>
<li class="cl-combobox-option is-active" role="option" id="positions-list-opt-3" aria-selected="true">
<span>Gerente de Ventas</span>
<span class="cl-combobox-option-meta">Ventas</span>
</li>
</ul>
</div>
Sin resultados
Cuando el filtro no matchea nada, mostrá una única fila no interactiva con .cl-combobox-empty.
- No se encontraron resultados
<ul class="cl-combobox-list" role="listbox">
<li class="cl-combobox-empty">No se encontraron resultados</li>
</ul>
Contrato de comportamiento (lo implementa la app)
- Abrir: click o foco en el input muestra la lista completa; digitar filtra.
- Teclado:
↓/↑mueven el foco virtual (.is-active+aria-activedescendant),Enterselecciona,Escapecierra,Alt+↓abre sin mover el foco. - Click fuera cierra la lista; los clicks dentro del componente no la cierran.
aria-expandedrefleja el estado de la lista en el input.- Al seleccionar, sincronizá el
<select>oculto y despacháchangesobre él para que el resto del form reaccione.