Elementy niestandardowe powiązane z formularzem

Nie używaj ukrytego elementu <input>, aby wyświetlić wartość elementu niestandardowego w formularzu. Używaj elementów niestandardowych powiązanych z formularzem (FACE): dzięki formAssociated i ElementInternals element przesyła własną wartość, uczestniczy w walidacji i odzwierciedla stan formularza w sposób natywny.

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

Najważniejsze kwestie:

  • static formAssociated = true jest WYMAGANY; odblokowuje metody ElementInternals związane z formularzem i cykl życia formXxxCallback.
  • setFormValue(value) to element, który przesyła dane, bez konieczności podawania ukrytych danych wejściowych. W przypadku elementów sterujących z wieloma wartościami przekaż FormData.
  • setValidity(flags, message, anchor) integruje się z natywną walidacją ograniczeń: formularz nie zostanie przesłany, jeśli jest nieprawidłowy, stosowane są :invalid/:valid, a element kotwicy otrzymuje fokus w raporcie. Zadzwoń pod numer setValidity({}), aby wyjaśnić sprawę. Pseudoklasy :user-valid/:user-invalid uwzględniające interakcje nie są spójnie powiązane z elementami powiązanymi z formularzem w różnych przeglądarkach. Zanim zaczniesz na nich polegać, sprawdź, czy są obsługiwane, zamiast zakładać, że śledzą interakcje użytkownika w taki sam sposób jak w przypadku elementów natywnych.
  • Zaimplementuj formResetCallback, formDisabledCallback i formStateRestoreCallback, aby element zachowywał się jak natywny element sterujący podczas resetowania, fieldset[disabled] i przywracania autouzupełniania wstecz/do przodu. W formDisabledCallback(disabled) wyłącz elementy sterujące (lub usuń je z kolejności tabulacji i ignoruj dane wejściowe), zamiast ustawiać disabled na hoście – ustawienie atrybutu hosta w wywołaniu zwrotnym uniemożliwia ponowne włączenie, gdy element nadrzędny <fieldset> zostanie ponownie włączony, a host już automatycznie pasuje do :disabled.
  • Postępuj zgodnie z konwencjami platformy dotyczącymi wysyłanych zdarzeń i preferuj wbudowane zdarzenia input i change zamiast niestandardowych. input jest wywoływane, gdy wartość zmienia się w bezpośrednim wyniku działania użytkownika (wpisywanie, przeciąganie suwaka); change jest wywoływane raz na strumień zmian, gdy użytkownik zatwierdza zmianę (zwolnienie suwaka, rozmycie pola). Utwórz je odpowiednio za pomocą InputEvent i Event.

Strategie zastępcze

Zanim zaczniesz korzystać z funkcji, sprawdź, czy jest obsługiwana przez static formAssociated: 'attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype. Jeśli jest niedostępny, użyj ukrytego wizualnie natywnego elementu <input> zsynchronizowanego z wartością elementu sterującego, aby formularz nadal był przesyłany – to jedyny przypadek, w którym wzorzec ukrytego pola wejściowego jest uzasadniony.