Formularbezogene benutzerdefinierte Elemente

Verwenden Sie kein ausgeblendetes <input>, um den Wert eines benutzerdefinierten Steuerelements in einem Formular anzuzeigen. Verwenden Sie Form-Associated Custom Elements (FACE): Mit formAssociated und ElementInternals sendet das Element seinen eigenen Wert, nimmt an der Validierung teil und spiegelt den Formularstatus nativ wider.

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

Wichtige Punkte:

  • static formAssociated = true ist ERFORDERLICH. Dadurch werden die formularbezogenen ElementInternals-Methoden und der formXxxCallback-Lebenszyklus freigeschaltet.
  • setFormValue(value) wird gesendet, ohne dass eine ausgeblendete Eingabe erforderlich ist. Übergeben Sie ein FormData für Steuerelemente mit mehreren Werten.
  • setValidity(flags, message, anchor) lässt sich in die native Einschränkungsvalidierung einbinden: Das Formular wird nicht gesendet, wenn es ungültig ist, :invalid/:valid gelten und das Ankerelement wird beim Melden fokussiert. Rufe setValidity({}) auf, um die Daten zu löschen. Die interaktionssensitiven Pseudoklassen :user-valid/:user-invalid sind in den verschiedenen Browsern nicht einheitlich mit formularbezogenen Elementen verknüpft. Prüfen Sie die Unterstützung, bevor Sie sich darauf verlassen, anstatt davon auszugehen, dass sie die Nutzerinteraktion wie bei nativen Steuerelementen erfassen.
  • Implementieren Sie formResetCallback, formDisabledCallback und formStateRestoreCallback, damit sich das Element beim Zurücksetzen, fieldset[disabled] und beim Wiederherstellen des automatischen Ausfüllens beim Vor- und Zurückgehen wie ein natives Steuerelement verhält. Deaktivieren Sie in formDisabledCallback(disabled) interne Steuerelemente (oder entfernen Sie sie aus der Tab-Reihenfolge und ignorieren Sie Eingaben), anstatt disabled auf dem Host festzulegen. Wenn Sie das Host-Attribut im Callback festlegen, wird die erneute Aktivierung verhindert, wenn ein übergeordnetes <fieldset>-Element wieder aktiviert wird, und der Host entspricht bereits automatisch :disabled.
  • Folgen Sie den Plattformkonventionen für gesendete Ereignisse und bevorzugen Sie die integrierten Ereignisse input und change gegenüber benutzerdefinierten Ereignissen. input wird ausgelöst, wenn sich der Wert als direkte Folge einer Nutzeraktion ändert (Eingabe, Ziehen eines Schiebereglers). change wird einmal pro Änderungsstream ausgelöst, wenn der Nutzer die Änderung bestätigt (Loslassen des Schiebereglers, Verlassen des Felds). Erstellen Sie sie mit InputEvent bzw. Event.

Fallback-Strategien

Prüfen Sie die Unterstützung von static formAssociated, bevor Sie sich darauf verlassen: 'attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype. Wenn es nicht verfügbar ist, greifen Sie auf ein visuell ausgeblendetes natives <input>-Element zurück, das mit dem Wert des Steuerelements synchronisiert ist, damit das Formular trotzdem gesendet wird. Dies ist der einzige Fall, in dem das Muster für ausgeblendete Eingaben gerechtfertigt ist.