العناصر المخصّصة المرتبطة بالنماذج

لا تلجأ إلى استخدام عنصر <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 التي تراعي التفاعل بشكل متسق مع العناصر المرتبطة بالنماذج في جميع المتصفحات، لذا يجب التحقق من توافقها قبل الاعتماد عليها بدلاً من افتراض أنها تتبع تفاعل المستخدم بالطريقة نفسها التي تتبعها عناصر التحكم الأصلية.
  • نفِّذ formResetCallback وformDisabledCallback وformStateRestoreCallback لكي يتصرف العنصر كعنصر تحكّم أصلي عند إعادة الضبط وfieldset[disabled] واستعادة الملء التلقائي للخلف/للأمام. في formDisabledCallback(disabled)، يجب إيقاف عناصر التحكّم الداخلية (أو إزالتها من ترتيب علامات التبويب وتجاهُل الإدخال) بدلاً من ضبط disabled على المضيف، لأنّ ضبط سمة المضيف داخل دالة الاستدعاء يمنع إعادة التفعيل عند إعادة تفعيل <fieldset> الأصل، ويتطابق المضيف تلقائيًا مع :disabled.
  • اتّبِع قواعد المنصة للأحداث المُرسَلة واستخدِم الحدثَين المضمّنَين input وchange بدلاً من الأحداث المخصّصة. يتم تنشيط input عندما تتغيّر القيمة كنتيجة مباشرة لإجراء من المستخدم (الكتابة أو سحب شريط التمرير)، بينما يتم تنشيط change مرة واحدة لكل سلسلة من التغييرات، عندما ينفّذ المستخدم الإجراء (تحرير شريط التمرير أو تمويه الحقل). ويمكنك إنشاؤهما باستخدام InputEvent وEvent على التوالي.

استراتيجيات احتياطية

يجب رصد الميزات من خلال دعم static formAssociated قبل الاعتماد عليها: 'attachInternals' in HTMLElement.prototype && 'setFormValue' in ElementInternals.prototype. وفي حال عدم توفّره، يمكنك استخدام عنصر <input> أصلي مخفي بصريًا ومتزامن مع قيمة عنصر التحكّم لكي يستمر إرسال النموذج، وهي الحالة الوحيدة التي يكون فيها نمط الإدخال المخفي مبررًا.