Non utilizzare un elemento <input> nascosto per mostrare il valore di un controllo personalizzato in un modulo. Utilizza gli elementi personalizzati associati ai moduli (FACE): con formAssociated e ElementInternals, l'elemento invia il proprio valore, partecipa alla convalida e riflette lo stato del modulo in modo nativo.
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);
Punti chiave:
static formAssociated = trueè OBBLIGATORIO; sblocca i metodiElementInternalsrelativi ai moduli e il ciclo di vitaformXxxCallback.setFormValue(value)è ciò che invia, senza bisogno di input nascosti. Passa unFormDataper i controlli con più valori.setValidity(flags, message, anchor)si integra con la convalida dei vincoli nativi: il modulo non verrà inviato se non è valido, si applicano:invalid/:valide l'elemento di ancoraggio riceve lo stato attivo nel report. Chiama il numerosetValidity({})per cancellare. Le pseudo-classi:user-valid/:user-invalidsensibili all'interazione non sono collegate in modo coerente agli elementi associati ai moduli nei vari browser. Verifica il supporto prima di fare affidamento su di esse, anziché presumere che monitorino l'interazione dell'utente come fanno per i controlli nativi.- Implementa
formResetCallback,formDisabledCallbackeformStateRestoreCallbackin modo che l'elemento si comporti come un controllo nativo in caso di reset,fieldset[disabled]e ripristino della compilazione automatica indietro/avanti. InformDisabledCallback(disabled), disattiva i controlli interni (o rimuovili dall'ordine di tabulazione e ignora l'input) anziché impostaredisabledsull'host. L'impostazione dell'attributo host all'interno del callback impedisce la riattivazione quando un elemento<fieldset>predecessore viene riattivato e l'host corrisponde già automaticamente a:disabled. - Segui le convenzioni della piattaforma per gli eventi inviati e preferisci gli eventi integrati
inputechangea quelli personalizzati.inputsi attiva quando il valore cambia come risultato diretto dell'azione dell'utente (digitazione, trascinamento di un cursore);changesi attiva una volta per flusso di modifiche, quando l'utente esegue il commit (rilascio del cursore, sfocatura del campo). Costruiscili rispettivamente conInputEventeEvent.
Strategie di riserva
Rileva le funzionalità con il supporto di static formAssociated prima di fare affidamento su di esse: 'attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype. Se non è disponibile, utilizza un elemento <input> nativo nascosto visivamente e sincronizzato con il valore del controllo, in modo che il modulo venga comunque inviato. Questo è l'unico caso in cui il pattern di input nascosto è giustificato.