ফর্ম-অ্যাসোসিয়েটেড কাস্টম এলিমেন্ট

কোনও ফর্মে কাস্টম কন্ট্রোলের ভ্যালু দেখানোর জন্য লুকানো <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>-এ ফিরে যান যাতে ফর্মটি এখনও জমা দেওয়া যায় — এটি এমন একটি কেস যেখানে লুকানো-ইনপুট প্যাটার্নটি যুক্তিযুক্ত।