Entrées personnalisées

Vous construisez votre première entrée personnalisée ?Lire le guide

FormKit inclut de nombreuses entrées prêtes à l'emploi, mais vous pouvez également définir vos propres entrées qui héritent automatiquement des fonctionnalités à valeur ajoutée de FormKit comme la validation, les messages d'erreur, la modélisation des données, le regroupement, les étiquettes, le texte d'aide et d'autres.

Modifier ou restructurer une entrée existante

Si votre cas d'utilisation nécessite des modifications d'une entrée existante, telles que le déplacement de sections, le changement ou la restructuration d'éléments HTML, etc., envisagez d'utiliser la fonctionnalité d'exportation d'entrée de FormKit.

Les entrées sont composées de deux parties essentielles :

  1. Une définition d'entrée.
  2. Le code de l'entrée : un schéma ou un composant.
Commencez par le guide

Si vous débutez avec les entrées personnalisées, envisagez de lire le guide "Créer une entrée personnalisée". Le contenu de cette page est destiné à expliquer les subtilités des entrées personnalisées pour des cas d'utilisation avancés comme la création d'un plugin ou d'une bibliothèque et n'est pas nécessaire pour de nombreux cas d'utilisation courants.

Enregistrer des entrées

Les nouvelles entrées nécessitent une définition d'entrée. Les définitions d'entrée peuvent être enregistrées avec FormKit de trois manières :

Définition d'entrée

Les définitions d'entrée sont des objets qui contiennent les informations nécessaires pour initialiser une entrée — comme quelles propriétés accepter, quel schéma ou composant afficher, et si des fonctions de fonctionnalités supplémentaires doivent être incluses. La structure de l'objet de définition est :

{
  // Type de nœud : input, group, ou list.
  type: 'input',
  // Schéma à afficher (objet schéma ou fonction qui retourne un objet)
  schema: [],
  // Un composant Vue à afficher (utiliser schéma _OU_ composant, mais pas les deux)
  component: YourComponent,
  // (optionnel) Propriétés spécifiques à l'entrée que le composant <FormKit> doit accepter.
  // doit être un tableau de chaînes en camelCase
  props: ['fooBar'],
  // (optionnel) Tableau de fonctions qui reçoivent le nœud.
  features: []
}

Utilisation de la propriété type

Créons l'entrée la plus simple possible — une qui affiche uniquement "Hello world".

Charger l'exemple en direct

Bien que cet exemple simpliste ne contienne aucun mécanisme d'entrée/sortie, il se qualifie toujours comme une entrée complète. Elle peut avoir une valeur, exécuter des règles de validation (elles ne seront pas affichées, mais elles peuvent bloquer les soumissions de formulaires) et exécuter des plugins. Fondamentalement, toutes les entrées sont des nœuds centraux et la définition de l'entrée fournit les mécanismes pour interagir avec ce nœud.

Entrées personnalisées globales

Pour utiliser votre entrée personnalisée n'importe où dans votre application via une chaîne "type" (ex : <FormKit type="foobar" />), vous pouvez ajouter une propriété inputs aux options defaultConfig. Les noms des propriétés de l'objet inputs deviennent les chaînes "type" disponibles pour le composant <FormKit> dans votre application.

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: {
        // La propriété sera le “type” dans <FormKit type="hello">
        hello: helloWorld,
      },
    })
  )
  .mount('#app')

Maintenant que nous avons défini notre entrée, nous pouvons l'utiliser n'importe où dans l'application :

Charger l'exemple en direct

Bibliothèques de plugins

L'exemple ci-dessus étend la bibliothèque @formkit/inputs (via defaultConfig). Cependant, une fonctionnalité puissante de FormKit est sa capacité à charger des bibliothèques d'entrées à partir de plusieurs plugins. Ces entrées peuvent ensuite être enregistrées partout où les plugins peuvent être définis :

  • Globalement
  • Par groupe
  • Par formulaire
  • Par liste
  • Par entrée

Refactorisons notre entrée hello world pour utiliser son propre plugin :

Charger l'exemple en direct
Héritage des plugins

Remarquez dans l'exemple ci-dessus que notre plugin a été défini sur un parent de l'élément qui l'a réellement utilisé ! Cela est dû à l'héritage des plugins — une fonctionnalité clé des plugins FormKit.

Schéma vs composant

Votre entrée peut être écrite en utilisant le schéma de FormKit ou un composant Vue générique. Chaque approche a ses avantages et inconvénients :

CodeAvantagesInconvénients
Vue
  • Courbe d'apprentissage (vous savez probablement comment écrire un composant Vue).
  • Outils de développement plus matures.
  • Rendu initial légèrement plus rapide.
  • Impossible d'utiliser la propriété :sections-schema pour modifier la structure.
  • Les plugins ne peuvent pas modifier le schéma pour changer le rendu.
  • Spécifique au framework (Vue uniquement).
  • Facile d'écrire des entrées qui ne s'intègrent pas bien dans l'écosystème FormKit.
Schéma
  • La structure peut être modifiée via la propriété :sections-schema (si vous le permettez).
  • Les plugins peuvent modifier/changer le rendu.
  • Indépendant du framework (portabilité future lorsque FormKit prendra en charge de nouveaux frameworks).
  • Compatibilité avec l'écosystème (idéal pour publier vos propres entrées open source).
  • Courbe d'apprentissage (nécessité de comprendre les schémas).
  • Rendu initial légèrement plus lent.
  • Outils de développement moins matures.
Composants dans les schémas

Même si vous préférez écrire une entrée personnalisée en utilisant un composant Vue standard, vous pouvez toujours utiliser un schéma dans votre définition d'entrée. Veuillez lire la section Utilisation de createInput pour étendre le schéma de base.

La conclusion principale est que si vous prévoyez d'utiliser une entrée personnalisée sur plusieurs projets — alors envisagez d'utiliser l'approche basée sur le schéma. Si votre entrée personnalisée ne sera utilisée que sur un seul projet et que la flexibilité n'est pas une préoccupation, utilisez un composant Vue.

Anticiper l'avenir

À l'avenir, FormKit pourrait s'étendre pour prendre en charge des frameworks supplémentaires (ex : React ou Svelte. Si cela vous intéresse, faites-le nous savoir !.) Écrire vos entrées en utilisant un schéma signifie que vos entrées seront compatibles (peut-être avec des changements minimes) avec ces frameworks également.

Entrées de schéma

Toutes les entrées de base de FormKit sont écrites en utilisant des schémas pour permettre la plus grande flexibilité possible. Vous avez deux options principales lorsque vous écrivez vos propres entrées de schéma :

Il est important de comprendre la structure de base d'une entrée "standard" de FormKit, qui est divisée en sections :

Adresse e-mail
🤟
test@example.com
🚀
Veuillez utiliser votre adresse e-mail scolaire.
Veuillez fournir une adresse e-mail valide.
Composition d'une entrée de texte standard de FormKit.

La section input dans le diagramme ci-dessus est généralement ce que vous allez remplacer lors de la création de vos propres entrées — en conservant les enveloppes, les étiquettes, le texte d'aide et les messages intacts. Cependant, si vous souhaitez également contrôler ces aspects, vous pouvez également écrire votre propre entrée à partir de zéro.

Utiliser createInput pour étendre le schéma de base

Pour créer des entrées en utilisant le schéma de base, vous pouvez utiliser l'utilitaire createInput() du package @formkit/vue. Cette fonction accepte 3 arguments :

  • (obligatoire) Un nœud de schéma ou un composant Vue, qu'il insère dans le schéma de base à la section input (voir le diagramme ci-dessus).
  • (optionnel) Un objet de propriétés de définition d'entrée à fusionner avec une définition auto-générée.
  • (optionnel) Un objet de schéma de sections (tout comme la prop sections-schema) à fusionner avec le schéma de base. Cela vous permet de modifier la structure d'enveloppe de l'entrée.

La fonction retourne une définition d'entrée prête à l'emploi.

Lorsque vous fournissez un composant comme premier argument, createInput générera un objet de schéma qui fait référence à votre composant dans le schéma de base. Votre composant recevra une seule prop context :

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

Lorsque vous fournissez un objet de schéma, votre schéma est directement injecté dans l'objet de schéma de base. Remarquez que notre exemple de bonjour prend maintenant en charge la sortie de fonctionnalités "standard" de FormKit comme les étiquettes, le texte d'aide et la validation :

Charger l'exemple en direct

Rédaction des entrées de schéma à partir de zéro

Il peut être judicieux d'écrire vos entrées complètement à partir de zéro sans utiliser aucune des fonctionnalités de base du schéma. Dans ce cas, fournissez simplement la définition de l'entrée de votre objet de schéma complet.

Charger l'exemple en direct

Dans l'exemple ci-dessus, nous avons pu recréer les mêmes fonctionnalités que l'exemple createInput — à savoir — l'étiquette, le texte d'aide et la sortie du message de validation. Cependant, il nous manque encore un certain nombre de fonctionnalités "standard" de FormKit, comme le support des slots. Si vous essayez de publier votre entrée ou de maintenir la compatibilité de l'API avec les autres entrées de FormKit, jetez un œil à la liste de vérification des entrées.

Entrées de composants

Entrées personnalisées vs enveloppes de composants Vue

Lors de la rédaction d'une entrée personnalisée FormKit en utilisant des composants Vue, il est recommandé de ne pas utiliser les composants FormKit à l'intérieur, les entrées personnalisées sont censées être écrites comme des entrées régulières avec l'avantage d'utiliser la prop de contexte FormKit pour ajouter la fonctionnalité requise par FormKit, si votre cas est d'utiliser un composant FormKit avec des valeurs par défaut, il est recommandé à la place d'utiliser une enveloppe de composant Vue et d'appeler directement ce composant, les entrées FormKit fonctionnent à tout niveau d'imbrication, ou vous pouvez également envisager d'utiliser la fonctionnalité d'exportation d'entrée de FormKit pour ajouter des fonctionnalités et changer les attrs et props.

Pour la plupart des utilisateurs, passer un composant Vue à createInput offre un bon équilibre entre personnalisation et fonctionnalités à valeur ajoutée. Si vous souhaitez vous éloigner complètement des entrées basées sur des schémas, vous pouvez passer un composant directement à une définition d'entrée.

Les entrées de composants reçoivent une seule prop — l'objet context. Il vous appartient ensuite d'écrire un composant qui englobe les fonctionnalités souhaitées de FormKit (étiquettes, texte d'aide, affichage des messages, etc.). Consultez la liste de vérification des entrées pour une liste de ce que vous voudrez produire.

Valeurs d'entrée et de sortie

Les entrées ont deux rôles critiques :

  • Recevoir l'entrée de l'utilisateur.
  • Afficher la valeur actuelle.

Recevoir une entrée

Vous pouvez recevoir une entrée de n'importe quelle interaction utilisateur et l'entrée peut définir sa valeur à n'importe quel type de données. Les entrées ne sont pas limitées aux chaînes et aux nombres — elles peuvent stocker avec plaisir des tableaux, des objets ou des structures de données personnalisées.

Fondamentalement, tout ce qu'une entrée doit faire est d'appeler node.input(value) avec une valeur. La méthode node.input() est automatiquement temporisée, alors n'hésitez pas à l'appeler fréquemment — comme à chaque frappe. Typiquement, cela ressemble à une liaison à l'événement input.

L'objet context comprend un gestionnaire d'entrée pour les types d'entrée de base : context.handlers.DOMInput. Cela peut être utilisé pour des entrées de type texte où la valeur de l'entrée est disponible à event.target.value. Si vous avez besoin d'un gestionnaire d'événements plus complexe, vous pouvez l'exposer en utilisant "features".

Toute interaction utilisateur peut être considérée comme un événement d'entrée. Pour de nombreuses entrées HTML natives, cette interaction est capturée avec l'événement input.

// Une entrée de texte HTML écrite dans le schéma :
{
  $el: 'input',
  attrs: {
    onInput: '$handlers.DOMInput'
  }
}

L'équivalent dans un template Vue :

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

Affichage des valeurs

Les entrées sont également responsables de l'affichage de la valeur actuelle. Typiquement, vous voudrez utiliser node._value ou $_value dans le schéma pour afficher une valeur. C'est la valeur "en direct" non temporisée. La valeur actuellement validée est node.value ($value). Lisez plus sur "l'établissement de la valeur" ici.

// Un champ de saisie de texte HTML écrit en schéma :
{
  $el: 'input',
  attrs: {
    onInput: '$handlers.DOMInput',
    value: '$_value'
  }
}

L'équivalent dans un template Vue :

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

La seule fois où l'entrée non validée _value devrait être utilisée est pour afficher la valeur sur l'entrée elle-même — dans tous les autres emplacements, il est important d'utiliser la valeur validée value.

Ajout de props

Les props standards de FormKit que vous pouvez passer au composant <FormKit> (comme label ou type) sont disponibles à la racine de l'objet contexte et dans les props du nœud central, et vous pouvez utiliser ces props dans votre schéma en les référençant directement dans les expressions (ex : $label). Toutes les props passées à un composant <FormKit> qui ne sont pas des props de nœud se retrouvent dans l'objet context.attrs (juste $attrs dans le schéma).

Si vous avez besoin de props supplémentaires, vous pouvez les déclarer dans votre définition d'entrée. Les props peuvent également être utilisées pour accepter de nouvelles props du composant <FormKit>, mais elles sont également utilisées pour l'état interne de l'entrée (un peu comme une ref dans un composant Vue 3).

FormKit utilise l'espace de noms props pour les deux usages (voir l'exemple d'autocomplétion ci-dessous pour un exemple de cela). Les props doivent toujours être définies en camelCase et utilisées dans vos templates Vue en kebab-case. Il y a 2 façons de définir des props :

  1. Notation par tableau.
  2. Notation par objet.
  3. La méthode node.addProps()

Notation par tableau

Charger l'exemple en direct

Lors de l'extension du schéma de base en utilisant l'assistant createInput, passez un second argument avec les valeurs de définition d'entrée à fusionner :

Charger l'exemple en direct

Notation par objet

La notation par objet vous donne un contrôle précis sur la façon dont vos props sont définies en vous permettant de :

  • Définir une valeur par défaut.
  • Définir des props boolean qui peuvent être passées sans valeur.
  • Définir des fonctions getter/setter personnalisées.
Charger l'exemple en direct

Méthode d'ajout de props (node.addProps())

Vous pouvez ajouter dynamiquement des props en utilisant la méthode node.addProps() dans tout environnement d'exécution où vous avez accès au nœud. Pour les entrées personnalisées, cela est particulièrement utile lorsqu'il est utilisé dans des fonctionnalités. La notation par tableau et la notation par objet sont prises en charge (voir ci-dessus).

Charger l'exemple en direct

Ajout de fonctionnalités

Les fonctionnalités sont le moyen privilégié d'ajouter des fonctionnalités à un type d'entrée personnalisé. Une "fonctionnalité" est simplement une fonction qui reçoit le nœud de base en argument. Effectivement, ce sont des plugins sans héritage (ils s'appliquent donc uniquement au nœud actuel). Vous pouvez utiliser les fonctionnalités pour ajouter des gestionnaires d'entrée, manipuler des valeurs, interagir avec des props, écouter des événements, et bien plus encore.

Les fonctionnalités sont définies dans un tableau pour encourager la réutilisation du code lorsque c'est possible. Par exemple, nous utilisons une fonctionnalité appelée “options” sur les entrées select, checkbox et radio.

Comme exemple, imaginons que vous voulez construire une entrée qui permet aux utilisateurs de saisir deux nombres, et que la valeur de l'entrée est la somme de ces deux nombres :

Charger l'exemple en direct

Support de TypeScript

FormKit est écrit en TypeScript et inclut des définitions de type pour toutes ses entrées de base. Si vous écrivez vos propres entrées et souhaitez fournir un support TypeScript, vous pouvez définir vos propres entrées en utilisant deux augmentations de module :

Ajout de types de props

La prop type du composant <FormKit> est une chaîne de caractères qui est utilisée comme la clé d'une union discriminée de props (FormKitInputProps). En augmentant ce type, vos entrées personnalisées peuvent définir leurs propres types de props. Pour ce faire, vous devez augmenter le type FormKitInputProps pour ajouter vos propres types personnalisés :

declare module '@formkit/inputs' {
  interface FormKitInputProps<Props extends FormKitInputs<Props>> {
    // Cette clé et le `type` doivent correspondre :
    'my-input': {
      // Définissez votre `type` d'entrée :
      type: 'my-input',
      // Définissez une prop optionnelle. Utilisez camelCase pour tous les noms de props :
      myOptionalProp?: string | number
      // Définissez une prop obligatoire
      superImportantProp: number
      // Définissez le type de valeur, cela devrait toujours être optionnel !
      value?: string | number
      // Utilisez le générique Prop pour inférer des informations d'un autre champ, remarquez
      // que nous utilisons un utilitaire "PropType" pour inférer le type de la `value` à partir du générique Props :
      someOtherProp?: PropType<Props, 'value'>
    }
  }
}

Ajout de types de slots

Si vous définissez vos propres sections (slots) dans votre entrée personnalisée, vous pouvez également ajouter le support TypeScript pour ceux-ci. Pour ce faire, vous devez augmenter le type FormKitInputSlots pour ajouter vos propres slots personnalisés :

declare module '@formkit/inputs' {
  interface FormKitInputProps<Props extends FormKitInputs<Props>> {
    'my-input' {
      type: 'my-input'
      // ... propriétés ici
    }
  }

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

Dans l'exemple ci-dessus, nous utilisons FormKitBaseSlots — un utilitaire TypeScript pour ajouter tous les slots "de base" que la plupart des entrées personnalisées implémentent, comme outer, label, help, message, etc. Cependant, vous pourriez également définir entièrement vos propres slots à partir de zéro, ou augmenter FormKitBaseSlots pour ajouter des slots supplémentaires (FormKitBaseSlots<Props> & YourCustomSlots).

declare module '@formkit/inputs' {
  // ... propriétés ici
  interface FormKitInputSlots<Props extends FormKitInputs<Props>> {
    'my-input': {
      // Ceci sera le *seul* slot disponible sur l'entrée my-input
      slotName: FormKitFrameworkContext & {
          // ceci sera disponible comme données de slot dans le slot `slotName`
          fooBar: string
        }
      }
    }
  }
}
Augmentez d'abord les propriétés

Pour augmenter les FormKitInputSlots, vous devez d'abord avoir écrit une augmentation pour FormKitInputProps qui inclut au moins la propriété type.

Exemples

Ci-dessous se trouvent quelques exemples d'entrées personnalisées. Ils ne sont pas destinés à être exhaustifs ou prêts pour la production, mais illustrent plutôt certaines fonctionnalités d'entrée personnalisée.

Entrée de texte simple

Ceci est l'entrée la plus simple possible et n'utilise aucune des structures DOM intégrées de FormKit et ne produit qu'une entrée de texte — cependant, c'est un membre entièrement fonctionnel du groupe dans lequel elle est imbriquée et capable de lire et d'écrire des valeurs.

Charger l'exemple en direct
Entrée DOM

Dans l'exemple ci-dessus, $handlers.DOMInput est une fonction de commodité intégrée pour (event) => node.input(event.target.value).

Entrée avec autocomplétion

Examinons un exemple légèrement plus complexe qui utilise createInput pour fournir toute la structure standard de FormKit tout en offrant une interface d'entrée personnalisée.

Charger l'exemple en direct

Liste de vérification des entrées

FormKit expose des dizaines de fonctionnalités à valeur ajoutée, même pour les entrées les plus banales. Lors de la création d'une entrée personnalisée pour un projet spécifique, vous n'avez besoin de mettre en œuvre que les fonctionnalités qui seront réellement utilisées sur ce projet. Cependant, si vous prévoyez de distribuer vos entrées à d'autres, vous voudrez vous assurer que ces fonctionnalités sont disponibles. Par exemple, l'entrée standard <FormKit type="text"> utilise le schéma suivant pour son élément 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',
  }
}

Il y a plusieurs fonctionnalités dans le schéma ci-dessus qui peuvent ne pas être immédiatement évidentes, comme le gestionnaire onBlur. La liste de vérification suivante est destinée à aider les auteurs d'entrées à couvrir toutes leurs 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}.