Makuco UI
ComponentesData Entry & Selection

Autocomplete

Campo de texto que exibe sugestões de uma fonte remota e confirma uma delas como valor. O `mk-autocomplete` emite a query digitada e renderiza exatamente as options que você devolver — ele não filtra.

O mk-autocomplete é o campo de busca com sugestões do Makuco UI. Use quando o conjunto de valores possíveis é grande demais para um mk-select — uma base de clientes, um catálogo de produtos, um serviço de CEP. O componente cuida do posicionamento do dropdown, da navegação por teclado e do contrato ARIA 1.2 de combobox.

O componente não filtra

A lista exibida é sempre exatamente options. Escute mkInput para buscar (ou filtrar localmente) e devolva o resultado em options. Passar três options com uma query que não casa nenhuma exibe as três.

Integração com Angular Forms

Para usar com ngModel ou FormControl, importe MkAutocompleteModule. O model liga em selectedValue — a seleção confirmada — e não no rascunho digitado. Veja Formulários com Angular.

Padrão

Digite ao menos dois caracteres. O exemplo abaixo faz uma request real à DummyJSON API e busca usuários pelo nome — sem simulação.

import { useState } from 'react';
import { MkAutocomplete } from '@db1/makuco-ui-react';
import type { AutocompleteOption } from '@db1/makuco-ui-core';

const [options, setOptions] = useState<AutocompleteOption[]>([]);
const [loading, setLoading] = useState(false);

<MkAutocomplete
  label="Colaborador"
  placeholder="Busque pelo nome"
  options={options}
  loading={loading}
  supportText="Busca em tempo real via DummyJSON API"
  onMkInput={async (event) => {
    if (!event.detail) return setOptions([]);
    setLoading(true);
    const res = await fetch(
      `https://dummyjson.com/users/search?q=${encodeURIComponent(event.detail)}&limit=8`
    );
    const data = await res.json();
    setOptions(
      (data.users ?? []).map((u) => ({ value: u.id, label: `${u.firstName} ${u.lastName}` }))
    );
    setLoading(false);
  }}
  onMkChange={(event) => console.log(event.detail)}
/>
// component.ts
import type { AutocompleteOption } from '@db1/makuco-ui-core';

options: AutocompleteOption[] = [];
loading = false;

async search(event: CustomEvent<string>) {
  if (!event.detail) { this.options = []; return; }
  this.loading = true;
  const res = await fetch(
    `https://dummyjson.com/users/search?q=${encodeURIComponent(event.detail)}&limit=8`
  );
  const data = await res.json();
  this.options = (data.users ?? []).map((u: any) => ({
    value: u.id,
    label: `${u.firstName} ${u.lastName}`,
  }));
  this.loading = false;
}
<mk-autocomplete
  label="Colaborador"
  placeholder="Busque pelo nome"
  [options]="options"
  [loading]="loading"
  support-text="Busca em tempo real via DummyJSON API"
  (mkInput)="search($event)"
  (mkChange)="save($event)"
></mk-autocomplete>

Os dois valores

Um autocomplete tem dois estados, e é isso que o separa de todo campo simples: o texto no campo e a seleção confirmada.

PropO que éVai para o formulário
valueO rascunho — o texto digitado.Nunca
selectedValueA seleção confirmada — o optionValue da option escolhida.Sim, via setFormValue

A sincronização é de uma direção só: definir selectedValue escreve o label da option correspondente em value; digitar não mexe em selectedValue.

Para o label ser resolvido, a option precisa estar em options — ao carregar um registro existente, entregue os dois juntos.

// `options` já contém a option de `selectedValue`, senão o campo aparece vazio.
<MkAutocomplete label="Cidade" options={[{ value: 'cwb', label: 'Curitiba' }]} selectedValue="cwb" />
<mk-autocomplete label="Cidade" [options]="options" [(ngModel)]="cityId"></mk-autocomplete>

Filtro local

Se os dados já estão em memória, o filtro é uma linha no handler de mkInput.

<MkAutocomplete
  label="Cidade"
  options={options}
  onMkInput={(event) => {
    const query = event.detail.toLowerCase();
    setOptions(ALL_CITIES.filter((city) => city.label.toLowerCase().includes(query)));
  }}
/>

Reatribua o array: options.push(x) não muda a referência e o componente não re-renderiza.

filter(event: CustomEvent<string>) {
  const query = event.detail.toLowerCase();
  // Novo array — mutar in-place não dispara o re-render.
  this.options = ALL_CITIES.filter((c) => c.label.toLowerCase().includes(query));
}

minLength e debounce

minLength (padrão 2) e debounce (padrão 300) existem para não disparar uma request por tecla. minLength é gate de abertura, não só de emissão: com o rascunho abaixo do piso, o dropdown não abre nem que options esteja populado.

Para sinalizar o piso ao usuário, use supportText — o componente não gera uma frase automática.

<MkAutocomplete
  label="Cidade"
  options={options}
  minLength={3}
  debounce={600}
  supportText="Digite ao menos 3 caracteres"
/>
<mk-autocomplete
  label="Cidade"
  [options]="options"
  [minLength]="3"
  [debounce]="600"
  support-text="Digite ao menos 3 caracteres"
></mk-autocomplete>

Carregando

loading troca a lupa (ou o ×) por um spinner no trailing. Com options vazio, o dropdown não abre — só o spinner aparece; loading também suprime a mensagem de vazio, evitando que ela apareça antes da resposta chegar.

<MkAutocomplete label="Cidade" options={options} loading />
<mk-autocomplete label="Cidade" [options]="options" [loading]="true"></mk-autocomplete>

Sem resultado

Uma linha única com a lupa e a query interpolada, para o usuário reconhecer o que digitou. emptyMessage substitui o texto inteiro — sem interpolação, você monta a frase.

<MkAutocomplete
  label="Cidade"
  options={options}
  emptyMessage="Nenhuma cidade encontrada nesta região."
/>
<mk-autocomplete
  label="Cidade"
  [options]="options"
  empty-message="Nenhuma cidade encontrada nesta região."
></mk-autocomplete>

Valor livre

allowCustomValue confirma o rascunho como está — por Enter sem nada em foco, ou no blur — e emite mkChange com option: undefined.

Combinado com required, a obrigatoriedade passa a ser satisfeita por qualquer texto confirmado, não por uma option da lista. Se o backend precisa de um id da base, não use esta prop.

<MkAutocomplete
  label="Cidade"
  options={options}
  allowCustomValue
  onMkChange={(event) => {
    // event.detail.option é undefined quando o texto não veio da lista
    save(event.detail.value);
  }}
/>
<mk-autocomplete
  label="Cidade"
  [options]="options"
  [allowCustomValue]="true"
  (mkChange)="save($event)"
></mk-autocomplete>

Obrigatório e erro

required valida a seleção, não o texto: um campo com "Curi" digitado mas nada confirmado continua inválido. errorMessage põe o campo em estado inválido, com borda em --stroke-error e aria-invalid="true" no input.

<MkAutocomplete
  label="Cidade"
  options={options}
  required
  errorMessage="Selecione uma cidade da lista"
/>
<mk-autocomplete
  label="Cidade"
  [options]="options"
  [required]="true"
  error-message="Selecione uma cidade da lista"
></mk-autocomplete>

Desabilitado e somente leitura

Em ambos o dropdown nunca abre — nem por digitação, nem por seta, nem com open definido junto no primeiro render — e o × não é renderizado.

<MkAutocomplete label="Cidade" options={options} selectedValue="cwb" disabled />
<MkAutocomplete label="Cidade" options={options} selectedValue="cwb" readonly />
<mk-autocomplete label="Cidade" [options]="options" [disabled]="true"></mk-autocomplete>
<mk-autocomplete label="Cidade" [options]="options" [readonly]="true"></mk-autocomplete>

Regra de blur

Com allowCustomValue = false (o padrão), ao perder o foco:

  1. O rascunho casa exatamente o label da option em selectedValue → nada acontece.
  2. Existe selectedValue e o rascunho divergiu → o rascunho volta para o label da seleção.
  3. Não existe selectedValue → o rascunho é esvaziado.

Em todos os casos selectedValue fica intacto e mkChange não é emitido.

Props — mk-autocomplete

PropTipoPadrãoDescrição
labelstringObrigatória. Rótulo exibido acima do campo.
namestringundefinedAtributo name para envio em formulários.
valuestringundefinedO rascunho — o que está digitado. Mutável. Nunca é submetido.
selectedValueunknownundefinedA seleção confirmada. Mutável. É o que vai para setFormValue e o que required valida.
optionsAutocompleteOption[][]Lista exibida, exatamente como recebida. Reatribua o array para re-renderizar.
optionValuestring"value"Chave de cada objeto usada como valor confirmado.
optionLabelstring"label"Chave de cada objeto usada como texto exibido.
placeholderstringundefinedTexto exibido com o campo vazio.
supportTextstringundefinedTexto de apoio abaixo do campo quando não há erro.
errorMessagestringundefinedMensagem de erro. Quando definida, o campo entra em estado inválido.
requiredbooleanfalseMarca o campo como obrigatório. Valida selectedValue, não o rascunho.
requiredMessagestring"This field is required"Mensagem da validação nativa quando required e sem seleção.
disabledbooleanfalseDesabilita o campo. O dropdown nunca abre.
readonlybooleanfalseSomente leitura. O dropdown nunca abre e o × não é renderizado.
clearablebooleantrueExibe o × quando há texto no campo.
loadingbooleanfalseSpinner no trailing. Suprime a mensagem de vazio.
minLengthnumber2Mínimo de caracteres para emitir mkInput e abrir o dropdown ao digitar.
debouncenumber300Espera em ms após a última tecla antes de emitir mkInput.
allowCustomValuebooleanfalsePermite confirmar texto que não casa com nenhuma option.
emptyMessagestringundefinedSubstitui a mensagem de vazio. Sem interpolação.
openbooleanfalseEstado do dropdown. Mutável e refletido, para controle programático.
localestringundefinedTag BCP 47 para i18n. Recai para o lang do ancestral, depois <html lang>, depois pt-BR.

AutocompleteOption

CampoTipoDescrição
valueTValor confirmado quando a option é escolhida. A chave é configurável por optionValue.
labelstringTexto exibido na lista e escrito no campo ao confirmar. A chave é configurável por optionLabel.
disabledbooleanImpede a seleção. A option é anunciada como aria-disabled e pulada pela navegação.

Eventos

EventoPayloadDescrição
mkInputstring

A query digitada, após debounce ms e com ao menos minLength caracteres. Emitido imediatamente com "" ao limpar — é o sinal para descartar os resultados.

mkChangeAutocompleteChangeDetail

A seleção foi confirmada (clique ou Enter) ou desfeita (limpar). { value, option }; ao limpar value é undefined. Nunca emitido pela regra de blur em modo estrito.

mkClearvoid

O × foi acionado, ou Escape com o dropdown já fechado e texto no campo. Sempre acompanhado de mkChange e mkInput("").

mkOpenvoidO dropdown abriu. Uma vez por transição. Não borbulha.
mkClosevoidO dropdown fechou. Uma vez por transição. Não borbulha.
mkFocusvoidO campo recebeu foco.
mkBlurvoidO campo perdeu o foco, depois de o dropdown fechar e a regra de blur ser aplicada.

Tokens de componente

Sobrescreva para divergir do padrão sem afetar mk-input ou mk-search. Todos têm override em [data-theme="dark"].

VariávelDescrição
--autocomplete-bg-defaultFundo do campo
--autocomplete-bg-hoverFundo em hover
--autocomplete-bg-disabledFundo desabilitado
--autocomplete-bg-read-onlyFundo somente leitura
--autocomplete-dropdown-bgFundo do dropdown
--autocomplete-option-bg-hoverFundo da option em hover
--autocomplete-option-bg-activeFundo da option em foco de teclado
--autocomplete-option-bg-disabledFundo da option desabilitada

Acessibilidade

Segue o WAI-ARIA APG — Combobox, variante combobox editável com popup de listbox, em ARIA 1.2. O elemento focado é o <input>, e é ele que carrega role="combobox", aria-expanded, aria-autocomplete="list" e aria-activedescendant.

  • O foco DOM nunca sai do <input>. A navegação entre options é inteiramente virtual, via aria-activedescendant. As options não são focáveis e não entram na ordem de tabulação.
  • role="listbox" está no próprio <ul>, para que as options sejam filhas diretas do listbox na árvore de acessibilidade.
  • aria-expanded é "true" também quando o dropdown mostra só a mensagem de vazio, porque o popup está exibido.
  • aria-activedescendant está ausente quando nenhuma option está em foco. A lista começa sem pré-destaque, conforme o APG — Enter sem foco não confirma nada e não submete o formulário por acidente.
  • Uma região role="status" aria-live="polite" anuncia a contagem de resultados ("4 resultados disponíveis") e a mensagem de vazio com a query. Sem isso, a lista apareceria sem nenhum sinal para quem não vê.
  • O spinner de loading tem role="status" e rótulo acessível próprio.
  • aria-required, aria-invalid e aria-describedby acompanham required, errorMessage e supportText.
  • O × não é um stop de tabulação (tabindex="-1"), de propósito: um foco no meio do combobox, entre o input e a lista, atrapalha mais do que ajuda. O caminho de teclado para limpar é Escape com o dropdown fechado.

Teclado

TeclaComportamento
Fechado: abre e foca a primeira option. Aberto: próxima option habilitada. Sem wrap
Fechado: abre e foca a última option. Aberto: option habilitada anterior. Sem wrap
Alt + ↓Abre sem focar nenhuma option
EnterConfirma a option em foco. Sem foco e com allowCustomValue, confirma o rascunho. Sem foco e sem allowCustomValue, não faz nada — e com o dropdown aberto não submete o formulário
EscapeAberto: fecha, mantém o rascunho. Fechado com texto: limpa
Home / EndSemântica de texto — move o cursor no rascunho, não navega a lista
TabFecha, aplica a regra de blur, o foco segue para o próximo elemento

Não há typeahead sobre a lista: a lista já é o resultado da digitação.

On this page