양식 연결 맞춤 요소

맞춤 컨트롤의 값을 양식에 표시하기 위해 숨겨진 <input>로 대체하지 마세요. 양식 연결 맞춤 요소 (FACE) 사용: formAssociated 및 ElementInternals를 사용하면 요소가 자체 값을 제출하고, 유효성 검사에 참여하며, 양식 상태를 기본적으로 반영합니다.

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

핵심 사항:

  • static formAssociated = true는 필수이며 양식 관련 ElementInternals 메서드와 formXxxCallback 수명 주기를 잠금 해제합니다.
  • setFormValue(value)은 숨겨진 입력 없이 제출하는 요소입니다. 다중 값 컨트롤에 FormData를 전달합니다.
  • setValidity(flags, message, anchor)는 기본 제약 조건 검증과 통합됩니다. 유효하지 않은 경우 양식이 제출되지 않고, :invalid/:valid가 적용되며, 앵커 요소가 보고서에 포커스를 받습니다. setValidity({})를 호출하여 삭제합니다. 상호작용을 인식하는 :user-valid/:user-invalid 가상 클래스는 브라우저 전반에서 양식 관련 요소에 일관되게 연결되지 않습니다. 따라서 가상 클래스가 네이티브 컨트롤과 동일한 방식으로 사용자 상호작용을 추적한다고 가정하지 말고, 가상 클래스에 의존하기 전에 지원 여부를 확인하세요.
  • 재설정, fieldset[disabled], 뒤로/앞으로 자동 완성 복원 시 요소가 네이티브 컨트롤처럼 작동하도록 formResetCallback, formDisabledCallback, formStateRestoreCallback를 구현합니다. formDisabledCallback(disabled)에서 호스트에 disabled를 설정하는 대신 내부 컨트롤을 사용 중지하거나 탭 순서에서 삭제하고 입력을 무시합니다. 콜백 내에서 호스트 속성을 설정하면 상위 <fieldset>가 다시 사용 설정될 때 다시 사용 설정되지 않으며 호스트는 이미 :disabled와 자동으로 일치합니다.
  • 전송된 이벤트에 대한 플랫폼 규칙을 따르고 맞춤 이벤트보다 기본 제공 input 및 change 이벤트를 선호합니다. input는 사용자 작업 (입력, 슬라이더 드래그)의 직접적인 결과로 값이 변경될 때 실행됩니다. change는 사용자가 커밋할 때 (슬라이더 해제, 필드 흐리게 처리) 변경 스트림당 한 번 실행됩니다. 각각 InputEvent 및 Event로 구성합니다.

대체 전략

static formAssociated 지원을 사용하여 기능을 감지한 후 사용합니다('attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype). 사용할 수 없는 경우 컨트롤 값에 동기화된 시각적으로 숨겨진 네이티브 <input>로 대체하여 양식이 계속 제출되도록 합니다. 이는 숨겨진 입력 패턴이 정당화되는 한 가지 사례입니다.