Aan formulieren gekoppelde aangepaste elementen

Gebruik geen verborgen <input> om de waarde van een aangepast bedieningselement in een formulier te tonen. Gebruik aan formulieren gekoppelde aangepaste elementen (FACE): met formAssociated en ElementInternals dient het element de eigen waarde in, neemt het deel aan validatie en weerspiegelt het de formulierstatus op een native manier.

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);

Belangrijke punten:

  • static formAssociated = true is VERPLICHT. Hiermee worden de formuliergerelateerde ElementInternals-methoden en de formXxxCallback-levenscyclus ontgrendeld.
  • setFormValue(value) is wat wordt ingediend, zonder dat er verborgen invoer is vereist. Geef een FormData door voor bedieningselementen met meerdere waarden.
  • setValidity(flags, message, anchor) integreert met native beperkingsvalidatie: het formulier wordt niet ingediend als het ongeldig is, :invalid/:valid zijn van toepassing en het ankerelement krijgt de focus in het rapport. Bel setValidity({}) om te wissen. De interactiebewuste pseudoklassen :user-valid/:user-invalid zijn niet consistent verbonden met formuliergerelateerde elementen in verschillende browsers. Controleer de ondersteuning voordat je erop vertrouwt in plaats van aan te nemen dat ze de gebruikersinteractie bijhouden zoals ze dat doen voor systeemeigen bedieningselementen.
  • Implementeer formResetCallback, formDisabledCallback en formStateRestoreCallback zodat het element zich gedraagt als een systeemeigen bedieningselement bij resetten, fieldset[disabled] en herstel van automatisch invullen bij terug/vooruit. Zet in formDisabledCallback(disabled) interne bedieningselementen uit (of verwijder ze uit de tabvolgorde en negeer invoer) in plaats van disabled in te stellen op de host. Als je het hostkenmerk instelt in de callback, wordt het niet opnieuw aangezet als een bovenliggende <fieldset> opnieuw wordt aangezet en de host al automatisch overeenkomt met :disabled.
  • Volg de platformconventies voor verzonden gebeurtenissen en geef de voorkeur aan de ingebouwde gebeurtenissen input en change boven aangepaste gebeurtenissen. input wordt geactiveerd als de waarde verandert als direct gevolg van een gebruikersactie (typen, een schuifregelaar slepen). change wordt één keer per stream van wijzigingen geactiveerd wanneer de gebruiker de wijziging bevestigt (de schuifregelaar loslaten, het veld vervagen). Maak ze met respectievelijk InputEvent en Event.

Reservestrategieën

Voer een functiedetectie uit met static formAssociated-ondersteuning voordat je erop vertrouwt: 'attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype. Als deze niet beschikbaar is, gebruik je een visueel verborgen systeemeigen <input> die is gesynchroniseerd met de waarde van het bedieningselement, zodat het formulier nog steeds wordt ingediend. Dit is het enige geval waarin het patroon voor verborgen invoer gerechtvaardigd is.