N'utilisez pas un <input> masqué pour afficher la valeur d'un contrôle personnalisé dans un formulaire. Utilisez des éléments personnalisés associés à un formulaire (FACE) : avec formAssociated et ElementInternals, l'élément envoie sa propre valeur, participe à la validation et reflète l'état du formulaire de manière native.
class RatingInput extends HTMLElement {
// MANDATORY: opt in. Without this, attachInternals() form APIs are unavailable.
static formAssociated = true;
#internals;
#value = '';
constructor() {
super();
this.#internals = this.attachInternals();
this.attachShadow({ mode: 'open', delegatesFocus: true });
}
connectedCallback() {
// Validate initial state once attributes (such as `required`) are present.
this.#validate();
}
get value() { return this.#value; }
set value(v) {
this.#value = v;
// Push the value into the owning <form> for submission.
this.#internals.setFormValue(v);
this.#validate();
}
#validate() {
if (this.hasAttribute('required') && !this.#value) {
// Mark invalid and supply the native validation message + anchor.
this.#internals.setValidity(
{ valueMissing: true },
'Please choose a rating.',
this.shadowRoot.querySelector('[tabindex]'),
);
} else {
this.#internals.setValidity({}); // clear
}
}
// Optional FACE lifecycle hooks:
formResetCallback() { this.value = ''; }
formDisabledCallback(disabled) {
// Disable internal controls so they leave the tab order and ignore clicks.
// Do NOT toggle `disabled` on `this`, or a `<fieldset disabled>` toggle will
// leave the host permanently stuck with its own `disabled` attribute.
for (const el of this.shadowRoot.querySelectorAll('button')) {
el.disabled = disabled;
}
}
formStateRestoreCallback(state) { this.value = state; }
}
customElements.define('rating-input', RatingInput);
Points essentiels :
static formAssociated = trueest OBLIGATOIRE. Il débloque les méthodesElementInternalsliées au formulaire et le cycle de vieformXxxCallback.setFormValue(value)est l'élément qui envoie le formulaire, sans qu'une entrée masquée soit nécessaire. Transmettez unFormDatapour les commandes à valeurs multiples.setValidity(flags, message, anchor)s'intègre à la validation des contraintes natives : le formulaire ne sera pas envoyé s'il n'est pas valide,:invalid/:valids'appliquent et l'élément d'ancrage reçoit le focus sur le rapport. AppelezsetValidity({})pour effacer. Les pseudo-classes:user-valid/:user-invalidsensibles aux interactions ne sont pas systématiquement associées aux éléments associés à un formulaire dans tous les navigateurs. Vérifiez la compatibilité avant de vous y fier, plutôt que de supposer qu'elles suivent l'interaction de l'utilisateur comme elles le font pour les commandes natives.- Implémentez
formResetCallback,formDisabledCallbacketformStateRestoreCallbackpour que l'élément se comporte comme un contrôle natif lors de la réinitialisation, defieldset[disabled]et de la restauration de la saisie automatique précédente/suivante. DansformDisabledCallback(disabled), désactivez les commandes internes (ou supprimez-les de l'ordre de tabulation et ignorez l'entrée) plutôt que de définirdisabledsur l'hôte. Définir l'attribut de l'hôte dans le rappel empêche la réactivation lorsqu'un ancêtre<fieldset>est réactivé, et l'hôte correspond déjà automatiquement à:disabled. - Suivez les conventions de la plate-forme pour les événements distribués et privilégiez les événements
inputetchangeintégrés aux événements personnalisés.inputse déclenche lorsque la valeur change à la suite d'une action de l'utilisateur (saisie, déplacement d'un curseur).changese déclenche une fois par flux de modifications, lorsque l'utilisateur valide (relâchement du curseur, floutage du champ). Créez-les respectivement avecInputEventetEvent.
Stratégies de remplacement
Détectez les fonctionnalités avec la compatibilité static formAssociated avant de vous y fier : 'attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype. Lorsqu'il n'est pas disponible, utilisez un <input> natif masqué visuellement et synchronisé avec la valeur de la commande pour que le formulaire soit toujours envoyé. C'est le seul cas où le modèle d'entrée masquée est justifié.