Jangan kembali ke <input> tersembunyi untuk menampilkan nilai kontrol kustom ke formulir. Gunakan Elemen Kustom Terkait Formulir (FACE): dengan formAssociated dan ElementInternals, elemen akan mengirimkan nilainya sendiri, berpartisipasi dalam validasi, dan mencerminkan status formulir secara native.
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);
Poin-poin utama:
static formAssociated = truebersifat WAJIB; metode ini membuka metodeElementInternalsterkait formulir dan siklus prosesformXxxCallback.setFormValue(value)adalah elemen yang mengirimkan, tanpa memerlukan input tersembunyi. TeruskanFormDatauntuk kontrol multi-nilai.setValidity(flags, message, anchor)terintegrasi dengan validasi batasan native: formulir tidak akan dikirim jika tidak valid,:invalid/:validberlaku, dan elemen anchor menerima fokus pada laporan. PanggilsetValidity({})untuk menghapus. Class pseudo:user-valid/:user-invalidyang mendukung interaksi tidak terhubung secara konsisten untuk elemen terkait formulir di seluruh browser — verifikasi dukungan sebelum mengandalkannya, bukan berasumsi bahwa class tersebut melacak interaksi pengguna seperti yang dilakukan untuk kontrol native.- Terapkan
formResetCallback,formDisabledCallback, danformStateRestoreCallbackagar elemen berperilaku seperti kontrol native saat direset,fieldset[disabled], dan pemulihan isi otomatis kembali/maju. DiformDisabledCallback(disabled), nonaktifkan kontrol internal (atau hapus dari urutan tab dan abaikan input) daripada menetapkandisabledpada host — menetapkan atribut host di dalam callback akan mencegah pengaktifan kembali saat ancestor<fieldset>diaktifkan kembali, dan host sudah cocok dengan:disabledsecara otomatis. - Ikuti konvensi platform untuk peristiwa yang dikirim dan pilih peristiwa
inputdanchangebawaan daripada peristiwa kustom.inputdiaktifkan saat nilai berubah sebagai akibat langsung dari tindakan pengguna (mengetik, menarik penggeser);changediaktifkan sekali per aliran perubahan, saat pengguna melakukan tindakan (melepaskan penggeser, memburamkan kolom). Buat denganInputEventdanEvent.
Strategi penggantian
Deteksi fitur dengan dukungan static formAssociated sebelum mengandalkannya: 'attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype. Jika tidak tersedia, gunakan <input> native yang disembunyikan secara visual dan disinkronkan ke nilai kontrol sehingga formulir tetap dikirimkan — satu kasus yang membenarkan pola input tersembunyi.