Entradas personalizadas

Construindo sua primeira entrada personalizada?Leia o guia

O FormKit inclui várias entradas prontas para uso, mas você também pode definir suas próprias entradas que herdam automaticamente os recursos de valor agregado do FormKit, como validação, mensagens de erro, modelagem de dados, agrupamento, rótulos, texto de ajuda e outros.

Modificar ou reestruturar uma entrada existente

Se o seu caso de uso requer modificações em uma entrada existente, como mover seções, alterar ou reestruturar elementos HTML, etc., considere usar o recurso de exportação de entrada do FormKit.

As entradas são compostas por duas partes essenciais:

  1. Uma definição de entrada.
  2. O código da entrada: um esquema ou um componente.
Comece pelo guia

Se você está apenas começando com entradas personalizadas, considere ler o guia “Criar uma entrada personalizada”. O conteúdo desta página é destinado a explicar as complexidades das entradas personalizadas para casos de uso avançados, como a criação de um plugin ou biblioteca, e não é necessário para muitos casos de uso comuns.

Registrando entradas

Novas entradas requerem uma definição de entrada. As definições de entrada podem ser registradas com o FormKit de três maneiras:

Definição de entrada

As definições de entrada são objetos que contêm as informações necessárias para inicializar uma entrada — como quais propriedades aceitar, qual esquema ou componente renderizar e se quaisquer funções de recursos adicionais devem ser incluídas. A estrutura do objeto de definição é:

{
  // Tipo de nó: input, group ou list.
  type: 'input',
  // Esquema para renderizar (objeto de esquema ou função que retorna um objeto)
  schema: [],
  // Um componente Vue para renderizar (use esquema _OU_ componente, mas não ambos)
  component: YourComponent,
  // (opcional) Propriedades específicas da entrada que o componente <FormKit> deve aceitar.
  // deve ser um array de strings em camelCase
  props: ['fooBar'],
  // (opcional) Array de funções que recebem o nó.
  features: []
}

Usando a propriedade type

Vamos fazer o input mais simples possível — um que apenas exibe "Hello world".

Carregar exemplo ao vivo

Embora este exemplo simplista não contenha nenhum mecanismo de entrada/saída, ele ainda se qualifica como um input completo. Ele pode ter um valor, executar regras de validação (elas não serão exibidas, mas podem bloquear envios de formulários) e executar plugins. Fundamentalmente, todos os inputs são nós centrais e a definição do input fornece os mecanismos para interagir com esse nó.

Inputs personalizados globais

Para usar seu input personalizado em qualquer lugar da sua aplicação através de uma string "type" (ex: <FormKit type="foobar" />), você pode adicionar uma propriedade inputs às opções de defaultConfig. Os nomes das propriedades do objeto inputs se tornam as strings "type" disponíveis para o componente <FormKit> na sua aplicação.

import { createApp } from 'vue'
import App from 'App.vue'
import { plugin, defaultConfig } from '@formkit/vue'

const helloWorld = {
  type: 'input',
  schema: ['Hello world'],
}

createApp(App)
  .use(
    plugin,
    defaultConfig({
      inputs: {
        // A propriedade será o “type” em <FormKit type="hello">
        hello: helloWorld,
      },
    })
  )
  .mount('#app')

Agora que definimos nosso input, podemos usá-lo em qualquer lugar da aplicação:

Carregar exemplo ao vivo

Bibliotecas de plugins

O exemplo acima estende a biblioteca @formkit/inputs (via defaultConfig). No entanto, um recurso poderoso do FormKit é sua capacidade de carregar bibliotecas de inputs de múltiplos plugins. Esses inputs podem então ser registrados em qualquer lugar onde plugins podem ser definidos:

  • Globalmente
  • Por grupo
  • Por formulário
  • Por lista
  • Por input

Vamos refatorar nosso input hello world para usar seu próprio plugin:

Carregar exemplo ao vivo
Herança de plugins

Observe no exemplo acima que nosso plugin foi definido em um elemento pai daquele que realmente o usou! Isso é graças à herança de plugins — um recurso central dos plugins do FormKit.

Esquema vs componente

Seu input pode ser escrito usando o esquema do FormKit ou um componente Vue genérico. Existem prós e contras para cada abordagem:

CódigoPrósContras
Vue
  • Curva de aprendizado (você provavelmente sabe como escrever um componente Vue).
  • Ferramentas de desenvolvimento mais maduras.
  • Renderização inicial um pouco mais rápida.
  • Não é possível usar a propriedade :sections-schema para modificar a estrutura.
  • Plugins não podem modificar o esquema para alterar a saída renderizada.
  • Específico do framework (apenas Vue).
  • Fácil de escrever inputs que não se integram bem com o ecossistema FormKit.
Esquema
  • A estrutura pode ser modificada via a propriedade :sections-schema (se você permitir).
  • Plugins podem modificar/mudar a saída renderizada.
  • Agnóstico ao framework (portabilidade futura para quando o FormKit suportar novos frameworks).
  • Compatibilidade com o ecossistema (ótimo para publicar seus próprios inputs de código aberto).
  • Curva de aprendizado (necessidade de entender esquemas).
  • Renderização inicial um pouco mais lenta.
  • Ferramentas de desenvolvimento menos maduras.
Componentes em esquemas

Mesmo que você prefira escrever um input personalizado usando um Componente Vue padrão, você ainda pode usar um esquema na definição do seu input. Por favor, leia a seção Usando createInput para estender o esquema base.

A principal conclusão é que se você planeja usar um input personalizado em vários projetos — então considere usar a abordagem baseada em esquema. Se o seu input personalizado será usado apenas em um único projeto e a flexibilidade não é uma preocupação, use um componente Vue.

Preparando para o futuro

No futuro, o FormKit pode se expandir para suportar frameworks adicionais (ex: React ou Svelte. Se isso é algo que lhe interessa, nos avise!.) Escrever seus inputs usando esquemas significa que seus inputs serão compatíveis (talvez com mudanças mínimas) com esses frameworks também.

Inputs de esquema

Todos os inputs principais do FormKit são escritos usando esquemas para permitir a maior flexibilidade possível. Você tem duas opções principais ao escrever seus próprios inputs de esquema:

É importante entender a estrutura básica de um input "padrão" do FormKit, que é dividido em seções:

Endereço de email
🤟
test@example.com
🚀
Por favor, use seu endereço de email escolar.
Por favor, forneça um email válido.
Composição de um input de texto padrão do FormKit.

A seção input no diagrama acima é tipicamente o que você vai substituir ao criar seus próprios inputs — mantendo os invólucros, rótulos, texto de ajuda e mensagens intactos. No entanto, se você também quiser controlar esses aspectos, pode escrever seu próprio input do zero.

Usando createInput para estender o esquema base

Para criar inputs usando o esquema base, você pode usar a utilidade createInput() do pacote @formkit/vue. Esta função aceita 3 argumentos:

  • (obrigatório) Um nó de esquema ou um componente Vue, que ele insere no esquema base na seção input (veja o diagrama acima).
  • (opcional) Um objeto de propriedades de definição de input para mesclar com um gerado automaticamente.
  • (opcional) Um objeto de esquema de seções (exatamente como a propriedade sections-schema) para mesclar com o esquema base. Isso permite modificar a estrutura de invólucro do input.

A função retorna uma definição de input pronta para uso.

Ao fornecer um componente como o primeiro argumento, createInput irá gerar um objeto de esquema que referencia seu componente dentro do esquema base. Seu componente receberá uma única propriedade context:

{
  $cmp: 'YourComponent',
  props: {
    context: '$node.context'
  }
}

Ao fornecer um objeto de esquema, seu esquema é injetado diretamente no objeto de esquema base. Observe que nosso exemplo de "olá mundo" agora suporta a saída de recursos "padrão" do FormKit, como rótulos, texto de ajuda e validação:

Carregar exemplo ao vivo

Escrevendo entradas de esquema do zero

Pode fazer sentido escrever suas entradas completamente do zero sem usar nenhum dos recursos do esquema base. Ao fazer isso, basta fornecer a definição de entrada com o seu objeto de esquema completo.

Carregar exemplo ao vivo

No exemplo acima, conseguimos recriar os mesmos recursos do exemplo createInput — ou seja — rótulo, texto de ajuda e saída de mensagem de validação. No entanto, ainda estamos perdendo uma série de recursos "padrão" do FormKit, como suporte a slots. Se você está tentando publicar sua entrada ou manter a compatibilidade da API com as outras entradas do FormKit, dê uma olhada na lista de verificação de entrada.

Entradas de componentes

Entradas personalizadas vs wrappers de componentes Vue

Ao escrever uma entrada personalizada do FormKit enquanto usa componentes Vue, é recomendado não usar os componentes do FormKit internamente, as entradas personalizadas devem ser escritas como entradas regulares com a vantagem de usar a propriedade de contexto do FormKit para adicionar a funcionalidade que o FormKit exige. Se o seu caso é usar um componente do FormKit com valores padrão, é recomendado, em vez disso, usar um wrapper de componente Vue e chamar diretamente esse componente. As entradas do FormKit funcionam em qualquer nível de aninhamento, ou você também pode considerar usar o recurso de exportação de entrada do FormKit para adicionar recursos e alterar atributos e props.

Para a maioria dos usuários, passar um componente Vue para createInput oferece um bom equilíbrio entre personalização e recursos de valor agregado. Se você deseja se desvincular completamente das entradas baseadas em esquema, pode passar um componente diretamente para uma definição de entrada.

As entradas de componentes recebem uma única propriedade — o objeto context. Depois, cabe a você escrever um componente que englobe os recursos desejados do FormKit (rótulos, texto de ajuda, exibição de mensagens, etc.). Confira a lista de verificação de entrada para uma lista do que você vai querer produzir.

Valores de entrada e saída

As entradas têm dois papéis críticos:

  • Receber a entrada do usuário.
  • Exibir o valor atual.

Recebendo entrada

Você pode receber entrada de qualquer interação do usuário e a entrada pode definir seu valor para qualquer tipo de dado. As entradas não são limitadas a strings e números — elas podem armazenar Arrays, Objetos ou estruturas de dados personalizadas.

Fundamentalmente, tudo o que uma entrada precisa fazer é chamar node.input(value) com um valor. O método node.input() é automaticamente debounced, então sinta-se à vontade para chamá-lo com frequência — como a cada tecla pressionada. Tipicamente, isso se parece com a vinculação ao evento input.

O objeto context inclui um manipulador de entrada para tipos de entrada básicos: context.handlers.DOMInput. Isso pode ser usado para entradas semelhantes a texto onde o valor da entrada está disponível em event.target.value. Se você precisar de um manipulador de eventos mais complexo, você pode expor isso usando "features".

Qualquer interação do usuário pode ser considerada um evento de entrada. Para muitas entradas HTML nativas, essa interação é capturada com o evento de entrada.

// Uma entrada de texto HTML escrita em esquema:
{
  $el: 'input',
  attrs: {
    onInput: '$handlers.DOMInput'
  }
}

O equivalente em um template Vue:

<template>
  <input @input="context.DOMInput" />
</template>

Exibindo valores

Entradas também são responsáveis por exibir o valor atual. Tipicamente, você vai querer usar o node._value ou $_value no esquema para exibir um valor. Este é o valor "ao vivo" não debounced. O valor atualmente confirmado é node.value ($value). Leia mais sobre "assentamento de valor" aqui.

// Um input de texto HTML escrito em esquema:
{
  $el: 'input',
  attrs: {
    onInput: '$handlers.DOMInput',
    value: '$_value'
  }
}

O equivalente em um template Vue:

<template>
  <input :value="context._value" @input="context.handlers.DOMInput" />
</template>
_value vs value

A única vez que o input não confirmado _value deve ser usado é para exibir o valor no próprio input — em todos os outros locais, é importante usar o valor confirmado value.

Adicionando props

As props padrão do FormKit que você pode passar para o componente <FormKit> (como label ou type) estão disponíveis na raiz do objeto de contexto e nas props do núcleo do nó, e você pode usar essas props no seu esquema referenciando-as diretamente em expressões (ex: $label). Qualquer prop passada para um componente <FormKit> que não seja props do nó acaba no objeto context.attrs (apenas $attrs no esquema).

Se você precisar de props adicionais, você pode declará-las na definição da sua entrada. Props também podem ser usadas para aceitar novas props do componente <FormKit>, mas também são usadas para o estado interno da entrada (muito como um ref em um componente Vue 3).

FormKit usa o namespace props para ambos os propósitos (veja o exemplo de autocomplete abaixo para um exemplo disso). Props devem sempre ser definidas em camelCase e usadas nos seus templates Vue com kebab-case. Existem 2 maneiras de definir props:

  1. Notação de array.
  2. Notação de objeto.
  3. O método node.addProps()

Notação de array

Carregar exemplo ao vivo

Ao estender o esquema base usando o auxiliar createInput, passe um segundo argumento com valores de definição de entrada para mesclar:

Carregar exemplo ao vivo

Notação de objeto

A notação de objeto te dá controle detalhado sobre como suas props são definidas, permitindo que você:

  • Defina um valor padrão.
  • Defina props boolean que podem ser passadas sem um valor.
  • Defina funções personalizadas de getter/setter.
Carregar exemplo ao vivo

Método de adicionar props (node.addProps())

Você pode adicionar props dinamicamente usando o método node.addProps() em qualquer ambiente de execução onde você tenha acesso ao nó. Para entradas personalizadas, isso é particularmente útil quando usado em funcionalidades. Tanto a notação de array quanto a notação de objeto são suportadas (veja acima).

Carregar exemplo ao vivo

Adicionando funcionalidades

Funcionalidades são a maneira preferida de adicionar funcionalidades a um tipo de entrada personalizado. Uma "funcionalidade" é simplesmente uma função que recebe o nó central como argumento. Efetivamente, elas são plugins sem herança (então elas só se aplicam ao nó atual). Você pode usar funcionalidades para adicionar manipuladores de entrada, manipular valores, interagir com props, ouvir eventos e muito mais.

Funcionalidades são definidas em um array para incentivar a reutilização de código quando possível. Por exemplo, nós usamos uma funcionalidade chamada “opções” em entradas do tipo select, checkbox e radio.

Como exemplo, vamos imaginar que você queira construir uma entrada que permite aos usuários inserir dois números, e o valor da entrada é a soma desses dois números:

Carregar exemplo ao vivo

Suporte ao TypeScript

O FormKit é escrito em TypeScript e inclui definições de tipo para todas as suas entradas centrais. Se você está escrevendo suas próprias entradas e gostaria de fornecer suporte ao TypeScript, você pode definir suas próprias entradas usando duas ampliações de módulo:

Adicionando tipos de prop

A prop type do componente <FormKit> é uma string que é usada como a chave de uma união discriminada de props (FormKitInputProps). Ao ampliar esse tipo, suas entradas personalizadas podem definir seus próprios tipos de prop. Para fazer isso, você deve ampliar o tipo FormKitInputProps para adicionar seus próprios tipos personalizados:

declare module '@formkit/inputs' {
  interface FormKitInputProps<Props extends FormKitInputs<Props>> {
    // Esta chave e o `type` devem coincidir:
    'my-input': {
      // Defina o `type` da sua entrada:
      type: 'my-input',
      // Defina uma prop opcional. Use camelCase para todos os nomes de prop:
      myOptionalProp?: string | number
      // Defina uma prop obrigatória
      superImportantProp: number
      // Defina o tipo de valor, isso sempre deve ser opcional!
      value?: string | number
      // Use o genérico Prop para inferir informações de outro campo, observe
      // que usamos uma utilidade "PropType" para inferir o tipo do `value` do genérico Props:
      someOtherProp?: PropType<Props, 'value'>
    }
  }
}

Adicionando tipos de slot

Se você definir suas próprias seções (slots) na sua entrada personalizada, você também pode adicionar suporte TypeScript para elas. Para fazer isso, você deve aumentar o tipo FormKitInputSlots para adicionar seus próprios slots personalizados:

declare module '@formkit/inputs' {
  interface FormKitInputProps<Props extends FormKitInputs<Props>> {
    'my-input' {
      type: 'my-input'
      // ... propriedades aqui
    }
  }

  interface FormKitInputSlots<Props extends FormKitInputs<Props>> {
    'my-input': FormKitBaseSlots<Props>
  }
}

No exemplo acima, usamos FormKitBaseSlots — uma utilidade TypeScript para adicionar todos os "slots básicos" que a maioria das entradas personalizadas implementam, como outer, label, help, message, etc. No entanto, você também poderia definir seus próprios slots completamente do zero, ou aumentar FormKitBaseSlots para adicionar slots adicionais (FormKitBaseSlots<Props> & YourCustomSlots).

declare module '@formkit/inputs' {
  // ... propriedades aqui
  interface FormKitInputSlots<Props extends FormKitInputs<Props>> {
    'my-input': {
      // Este será o *único* slot disponível na entrada my-input
      slotName: FormKitFrameworkContext & {
          // isso estará disponível como dados de slot no slot `slotName`
          fooBar: string
        }
      }
    }
  }
}
Aumente as propriedades primeiro

Para aumentar o FormKitInputSlots, você deve primeiro ter escrito um aumento para FormKitInputProps que pelo menos inclua a propriedade type.

Exemplos

Abaixo estão alguns exemplos de entradas personalizadas. Eles não são destinados a ser abrangentes ou prontos para produção, mas sim ilustrar algumas características de entrada personalizada.

Entrada de texto simples

Esta é a entrada mais simples possível e não aproveita nenhuma das estruturas DOM incorporadas do FormKit e apenas produz uma entrada de texto — no entanto, é um membro totalmente funcional do grupo em que está aninhado e capaz de ler e escrever valores.

Carregar exemplo ao vivo
Entrada DOM

No exemplo acima, o $handlers.DOMInput é uma função de conveniência integrada para (event) => node.input(event.target.value).

Entrada de autocompletar

Vamos dar uma olhada em um exemplo um pouco mais complexo que utiliza createInput para fornecer toda a estrutura padrão do FormKit, enquanto ainda fornece uma interface de entrada personalizada.

Carregar exemplo ao vivo

Lista de verificação de entrada

FormKit expõe dezenas de recursos valiosos até mesmo para as entradas mais comuns. Ao escrever uma entrada personalizada para um projeto específico, você só precisa implementar os recursos que serão realmente utilizados naquele projeto. No entanto, se você planeja distribuir suas entradas para outras pessoas, você vai querer garantir que esses recursos estejam disponíveis. Por exemplo, a entrada padrão <FormKit type="text"> usa o seguinte esquema para o seu elemento input:

{
  $el: 'input',
  bind: '$attrs',
  attrs: {
    type: '$type',
    disabled: '$disabled',
    class: '$classes.input',
    name: '$node.name',
    onInput: '$handlers.DOMInput',
    onBlur: '$handlers.blur',
    value: '$_value',
    id: '$id',
  }
}

Há vários recursos no esquema acima que podem não ser imediatamente óbvios, como o manipulador onBlur. A seguinte lista de verificação é destinada a ajudar os autores de entrada a cobrir todas as suas bases:

How is your input built?
  • The outermost wrapper element on all FormKit inputs.
  • The value of the label prop must be displayed and linked for accessibility with the for attribute.
  • Users can override the label slot.
  • Users can extend the label section using the label section key.
  • The value of the help prop must be displayed.
  • Users can override the help slot.
  • Users can extend the help section using the help section key.
  • Each message in the context.messages object must displayed if it is set to visible.
  • Users can override the messages slot.
  • Users can extend the messages section using the messages section key.
  • Users can override the message slot.
  • Users can extend the message section using the message section key..
  • Users can override the input slot.
  • The primary input element should include an id attribute (context.id).
  • The primary input element should include a name attribute (context.node.name).
  • The primary input element should call context.handlers.blur when blurred.
  • The primary input element should call node.input(value) when the user provides input. You can use context.handlers.DOMInput for text-like inputs.
  • The primary input element should display the current value of the input using context._value.
  • The primary input element should apply the disabled attribute when context.disabled is true.
  • All events bindings should be passed through. Use bind: '$attrs' in schemas.
  • Classes for all DOM elements should be applied using context.classes.{section-key}.